线程的上下文类加载器和普通的类加载器之间的区别

时间:2009-11-20 16:33:45

标签: java multithreading jvm classloader

线程的上下文类加载器和普通的类加载器之间有什么区别?

也就是说,如果Thread.currentThread().getContextClassLoader()getClass().getClassLoader()返回不同的类加载器对象,将使用哪一个?

4 个答案:

答案 0 :(得分:135)

每个类都将使用自己的类加载器来加载其他类。因此,如果ClassA.class引用ClassB.class,则ClassB需要位于ClassA或其父级的类加载器的类路径中。

线程上下文类加载器是当前线程的当前类加载器。可以从ClassLoaderC中的类创建对象,然后将其传递给ClassLoaderD拥有的线程。在这种情况下,如果对象需要加载其自己的类加载器上不可用的资源,则需要直接使用Thread.currentThread().getContextClassLoader()

答案 1 :(得分:85)

有一篇关于javaworld.com的文章解释了它的不同之处 => Which ClassLoader should you use

(1)

  

线程上下文类加载器提供了一个   后门周围的班级   授权计划。

     

以JNDI为例:它的胆量是   由bootstrap类实现   rt.jar(从J2SE 1.3开始),但是   这些核心JNDI类可能会加载JNDI   提供者由独立实施   供应商并可能部署在   应用程序的-classpath。这个   场景调用父级   classloader(原始的)   这种情况)加载一个可见的类   它的一个子类加载器(   例如,系统一)。正常的J2SE   代表团不起作用,而且   解决方法是制作核心JNDI   类使用线程上下文加载器,   从而有效地“挖掘”通过   中的类加载器层次结构   方向与正确相反   一行。

(2)来自同一来源:

  

这种困惑可能会继续存在   Java已经有一段时间了。使用任何J2SE API   动态资源加载任何   亲切并试着猜出哪个装载   它使用的策略。这是一个抽样:

     
      
  • JNDI使用上下文类加载器
  •   
  • Class.getResource()和Class.forName()使用当前的类加载器
  •   
  • JAXP使用上下文类加载器(从J2SE 1.4开始)
  •   
  • java.util.ResourceBundle使用调用者的当前类加载器
  •   
  • 通过java.protocol.handler.pkgs系统属性指定的URL协议处理程序仅在引导程序和系统类加载器中查找
  •   
  • Java Serialization API默认使用调用方的当前类加载器
  •   

答案 2 :(得分:82)

这不能回答原始问题,但由于问题是针对任何ContextClassLoader查询进行高度排名和链接的,我认为回答关于何时应该使用上下文类加载器的相关问题非常重要。简短回答:从不使用上下文类加载器!但是,当您必须调用缺少getClass().getClassLoader()参数的方法时,请将其设置为ClassLoader

当一个类的代码要求加载另一个类时,要使用的正确类加载器与调用者类(即getClass().getClassLoader())是相同的类加载器。这是99.9%的时间工作的方式因为this is what the JVM does itself第一次构造新类的实例,调用静态方法或访问静态字段。

如果要使用反射创建类(例如反序列化或加载可配置的命名类),执行反射的库应始终要求应用程序使用哪个类加载器,从应用程序接收ClassLoader作为参数。应用程序(知道需要构建的所有类)应该传递它getClass().getClassLoader()

获取类加载器的任何其他方法都是错误的。如果某个库使用诸如Thread.getContextClassLoader()sun.misc.VM.latestUserDefinedLoader()sun.reflect.Reflection.getCallerClass()之类的黑客攻击,那么这是由API不足引起的错误。基本上,Thread.getContextClassLoader()的存在只是因为设计ObjectInputStream API的人忘记接受ClassLoader作为参数,而这个错误一直困扰着Java社区。

也就是说,许多JDK类使用一些黑客来猜测一些类加载器的使用。有些使用ContextClassLoader(当你在共享线程池上运行不同的应用程序时失败,或者当你离开ContextClassLoader null时),有些人使用堆栈(当类的直接调用者本身时,它会失败)一个库),有些使用系统类加载器(这很好,只要它被记录为仅使用CLASSPATH中的类)或引导类加载器,并且一些使用上述技术的不可预测的组合(其中只会让事情变得更加混乱)。这导致了许多牙齿的哭泣和咬牙切齿。

使用这样的API时,首先尝试查找接受类加载器作为参数的方法的重载。如果没有合理的方法,请尝试在API调用之前设置ContextClassLoader(之后重置):

ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
    // call some API that uses reflection without taking ClassLoader param
} finally {
    Thread.currentThread().setContextClassLoader(originalClassLoader);
}

答案 3 :(得分:32)

添加到@David Roussel答案,类可以由多个类加载器加载。

让我们了解class loader的工作原理。

来自javarevisited的javin paul博客:

enter image description here

enter image description here

ClassLoader遵循三个原则。

授权原则

  

当需要时,用Java加载类。假设您有一个名为Abc.class的特定于应用程序的类,首先加载此类的请求将来到Application ClassLoader,它将委托给它的父ExtensionLoader,它进一步委托给Primordial或Bootstrap类加载器

  • Bootstrap ClassLoader 负责从rt.jar加载标准JDK类文件,它是Java中所有类加载器的父级。 Bootstrap类加载器没有任何父母。

  • 扩展ClassLoader 将类加载请求委托给其父级Bootstrap,如果不成功,则加载类表单jre / lib / ext目录或java.ext.dirs系统属性指向的任何其他目录

  • 系统或应用程序类加载器,它负责从CLASSPATH环境变量,-classpath或-cp命令行选项加载特定于应用程序的类,JAR中的Manifest文件的Class-Path属性。

  • 应用程序类加载器 Extension ClassLoader 的子项,由sun.misc.Launcher$AppClassLoader类实现。

注意: Bootstrap类加载器除外,它主要用C语言实现,所有Java类加载器都是使用java.lang.ClassLoader实现的。

可见性原则

  

根据可见性原则, Child ClassLoader 可以看到由 Parent ClassLoader 加载的类,但反之亦然。

唯一性原则

  

根据这个原则,父类加载的类不应再次由Child ClassLoader加载