Java类加载器:为什么首先搜索父类加载器?

19
在Java中,类加载器的正确行为应该是:

  1. 如果已经加载,则返回该类
  2. 调用父级loadClass()
  3. 尝试自己加载类。

因此,系统类路径中定义的类应始终首先被加载。Tomcat为每个war定义了一个类加载器,其中系统类加载器为其父级,因此,如果要加载类,则会首先查找系统类路径,然后再在war文件中定义的类路径中查找。

据我所知,这是出于两个原因:

  1. 避免使用不同版本的类出现问题。想象一下,如果我在war文件中重新定义了java.lang.Object,那将是一场噩梦。
  2. 避免对子类加载器产生依赖:系统类加载器不能依赖于子类加载器:例如,重新部署war文件将变得困难。

所以,问题是:

除了上述问题之外,实施不首先进行父级搜索的类加载器是否还存在其他陷阱?


1
请注意:java.lang.Object不会从WAR中加载。ClassLoader中有一个明确的检查java.lang。它只能从引导位置加载。 - Vladimir Dyuzhev
6个回答

17

Tomcat不会首先寻找父类加载器。实际上,它相反地:首先在webapp中查找,只有在没有找到时才会转而查找父类加载器(对于Tomcat 6/7来说是“lib”,对于Tomcat 5.5来说是“shared”)。这个规则的例外是系统类(我认为所有以java.*和javax.*开头的类),这些类只会在系统类加载器中查找。我相信他们这样做的原因是你提到的第一个原因。

所以基本上实现父优先策略与实现子优先策略都可以。两种策略都各有利弊。

我再给你一个理由,为什么要实现父优先:可以减少永久内存中加载的类的数量。想象一下,如果有多个Web应用程序使用同一个库。使用父优先,该库将被加载一次。使用子优先,它将被加载多次。
然而,使用父优先,所有的Web应用程序都需要使用相同版本的库,而使用子优先,则可能使用不同版本。


有趣的回答。我想知道实现父类最后加载器的最佳策略是什么。实现父类优先很简单,只需要实现findClass()方法即可。但是要实现父类最后,似乎你需要重写loadClass(String, boolean)方法,并且可能需要复制许多功能(除了类查找顺序)?谢谢。 - RAY
1
为什么需要实现自己的类加载器?你想解决哪些用例?这些是在开始讨论实际实现之前的第一个问题。通常,实现是由用例驱动的... - Tarlog

4

如果您尝试使用 myClassLoader.loadClass("[I"),您将会得到一个 java.lang.ClassNotFoundException 异常,这是非常令人惊讶的。 - Andrew Bate

3
我能想到的唯一另一个原因是确保类路径按预期工作。首先搜索父级实际上是将父级的类路径(即系统类路径)放在子级的类路径之前。因此,如果您在使用-cp启动jvm时指定了特定的jar,则它们将“隐藏”您尝试加载的任何存档中包含的jar。如果您不首先检查父项,则子项的类路径会遮盖父项。

2

然而,容器为什么要在一个虚拟机中托管多个应用程序还存在疑问。分离进程会更好一些。


  1. 用户群较小的大型应用程序可以分解为独立的部分并单独提交。
  2. 大型组织可以将服务器分配给部门功能,与该功能相关的所有应用程序都可以驻留在一个地方,从而节省大量硬件成本。
- pojo-guy

0

如果您根本不搜索您的父级,则将无法访问所有标准Java对象,很可能根本无法运行。

但是,在尝试父级之前,首先搜索自己的类加载器是合理的。如果您想覆盖上面某个类提供的行为,则需要以这种方式执行。

JVM不会这样做,因为首先搜索您的父级是最有意义的。不过,如果您知道自己在做什么,可以使用类加载器做一些非常有趣的事情。请查看Classworlds


0

Tomcat Web应用程序类加载器不遵守“优先使用父级”。如文档所述:

如上所述,Web应用程序类加载器与默认的Java委派模型不同(根据Servlet规范2.4版第9.7.2节Web应用程序类加载器的建议)。当处理从Web应用程序的WebappX类加载器加载类的请求时,此类加载器将首先查找本地存储库,而不是在查找之前委派。有例外情况。 JRE基础类的一部分无法被覆盖。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接