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

Posted

技术标签:

【中文标题】线程的上下文类加载器和普通类加载器的区别【英文标题】:Difference between thread's context class loader and normal classloader 【发布时间】:2010-12-18 19:19:31 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

为什么说ClassB必须在ClassA的loader(或者ClassA的loader的父母)的classpath上? ClassA 的加载器是不是可以覆盖loadClass(),这样即使ClassB 不在其类路径中,它也可以成功加载ClassB 确实,并不是所有的类加载器都有一个类路径。当写“ClassB需要在ClassA的类加载器的类路径上”时,我的意思是“ClassB需要由ClassA的类加载器加载”。 90% 的时间它们的意思是一样的。但是如果你没有使用基于 URL 的类加载器,那么只有第二种情况是正确的。 ClassA.class 引用ClassB.class”是什么意思? 当 ClassA 有 ClassB 的 import 语句,或者 ClassA 中有一个方法具有 ClassB 类型的局部变量时。如果 ClassB 尚未加载,这将触发加载。 我认为我的问题与这个话题有关。你觉得我的解决方案怎么样?我知道这不是一个好的模式,但我不知道如何解决它:***.com/questions/29238493/…【参考方案2】:

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

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

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

获取类加载器的任何其他方式都是不正确的。如果库使用了诸如Thread.getContextClassLoader()sun.misc.VM.latestUserDefinedLoader()sun.reflect.Reflection.getCallerClass() 之类的hack,则这是由API 缺陷引起的错误。基本上,Thread.getContextClassLoader() 的存在只是因为设计ObjectInputStream API 的人忘记接受ClassLoader 作为参数,而这个错误至今仍困扰着Java 社区。​​p>

也就是说,许多 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);

【讨论】:

是的,这是我要向任何提出问题的人指出的答案。 这个答案的重点是使用类加载器来加载类(通过反射或类似的方式实例化它们),而它的另一个目的是(事实上,我用过它的唯一目的个人)正在加载资源。是否适用相同的原则,或者在某些情况下您希望通过上下文类加载器而不是调用者类加载器获取资源? 请注意getClass().getClassLoader() 不一定与ThisClass.class.getClassLoader() 相同,除非ThisClassfinal,否则您知道子类不存在。【参考方案3】:

infoworld.com 上有一篇文章解释了不同之处 => Which ClassLoader should you use

(1)

线程上下文类加载器提供了一个 类加载周围的后门 委托方案。

以 JNDI 为例:它的胆量是 由引导类实现 rt.jar(从 J2SE 1.3 开始),但是 这些核心 JNDI 类可以加载 JNDI 由独立实施的提供者 供应商并可能部署在 应用程序的 -classpath。这 场景需要父母 类加载器(在 这种情况下)加载一个可见的类 它的子类加载器之一( 系统一,例如)。普通 J2SE 委托不起作用,并且 解决方法是制作核心JNDI 类使用线程上下文加载器, 从而有效地“隧道”通过 类加载器层次结构 与正确方向相反的方向 委托。

(2) 来自同一来源:

这种混乱可能会一直存在 Java 有一段时间了。采用任何 J2SE API 任何动态资源加载 种类并尝试猜测哪个加载 它使用的策略。这是一个示例:

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

【讨论】:

由于建议解决方法是让核心 JNDI 类使用线程上下文加载器,我不明白这在这种情况下有什么帮助。我们想使用父类加载器加载实现供应商类但它们对父类加载器不可见。那么我们如何使用父类加载它们,即使我们在线程的上下文类加载器中设置这个父类加载器。 @SAM,建议的解决方法实际上与您最后所说的完全相反。不是父 bootstrap 类加载器被设置为上下文类加载器,而是 Thread 正在设置的子 system 类路径类加载器。然后JNDI 类确保使用Thread.currentThread().getContextClassLoader() 加载类路径上可用的 JNDI 实现类。 “正常的 J2SE 委托不起作用”,我可以知道为什么它不起作用吗?因为Bootstrap ClassLoader只能从rt.jar加载类,不能从应用的-classpath加载类?对吗?【参考方案4】:

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

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

来自 javarevisited 中的 javin paul 博客:

ClassLoader 遵循三个原则。

委托原则

一个类在需要时在 Java 中加载。假设您有一个名为 Abc.class 的应用程序特定类,加载该类的第一个请求将来自 Application ClassLoader,后者将委托给其父 Extension ClassLoader,后者进一步委托给 Primordial 或 Bootstrap 类加载器

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

Extension 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 实现。

可见性原则

根据可见性原则,子类加载器可以看到父类加载器加载的类,反之则不然。

唯一性原则

根据这个原则,Parent加载的类不应该再被Child ClassLoader加载

【讨论】:

以上是关于线程的上下文类加载器和普通类加载器的区别的主要内容,如果未能解决你的问题,请参考以下文章

Java虚拟机(JVM)-- 类加载器和双亲委派机制

06-阿里面试题:Tomcat类加载设计 [线程上下文类加载器+破坏性双亲委派机制]

线程上下文类加载器分析与实现

05-说下类加载器和双亲委派机制

类加载器和双亲委派

JVM 类加载