【发布时间】:2010-12-18 19:19:31
【问题描述】:
线程的上下文类加载器和普通的类加载器有什么区别?
也就是说,如果Thread.currentThread().getContextClassLoader()和getClass().getClassLoader()返回不同的类加载器对象,会使用哪一个?
【问题讨论】:
标签: java multithreading jvm classloader
线程的上下文类加载器和普通的类加载器有什么区别?
也就是说,如果Thread.currentThread().getContextClassLoader()和getClass().getClassLoader()返回不同的类加载器对象,会使用哪一个?
【问题讨论】:
标签: java multithreading jvm classloader
每个类都将使用自己的类加载器来加载其他类。因此,如果 ClassA.class 引用 ClassB.class 则 ClassB 需要位于 ClassA 或其父类的类加载器的类路径中。
线程上下文类加载器是当前线程的当前类加载器。可以从ClassLoaderC 中的类创建对象,然后将其传递给ClassLoaderD 拥有的线程。在这种情况下,对象需要直接使用Thread.currentThread().getContextClassLoader(),如果它想加载它自己的类加载器上不可用的资源。
【讨论】:
ClassB必须在ClassA的loader(或者ClassA的loader的父母)的classpath上? ClassA 的加载器是不是可以覆盖loadClass(),这样即使ClassB 不在其类路径中,它也可以成功加载ClassB?
ClassA.class 引用ClassB.class”是什么意思?
这并没有回答原始问题,但由于该问题对于任何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() 相同,除非ThisClass 是final,否则您知道子类不存在。
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 默认使用调用者当前的类加载器
【讨论】:
bootstrap 类加载器被设置为上下文类加载器,而是 Thread 正在设置的子 system 类路径类加载器。然后JNDI 类确保使用Thread.currentThread().getContextClassLoader() 加载类路径上可用的 JNDI 实现类。
添加到@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加载
【讨论】: