深入剖析Tomcat类加载机制
1JVM类加载机制
JVM的ClassLoader通过Parent属性定义父子关系,可以形成树状结构。其中引导类、扩展类、系统类三个加载器是JVM内置的。它们的作用分别是:1)引导类加载器:使用native代码实现,在rt.jar等包中搜索运行JVM所需的类,例如java.lang等包下的类。2)扩展类加载器:负责载入标准扩展目录中的类,例如Sun的JVM的扩展目录是/jdk/jre/lib/ext。3)系统类加载器:默认的类加载器,搜索环境变量CLASSPATH中指明的路径。
2双亲委派模型
既然类加载器是树形结构,那加载类时就需要定义类到底由当前加载器还是父加载器去搜索加载。JVM加载模型的工作过程是: 如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。 每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的引导类加载器中。 只有父类加载无法完成这个请求时,子类加载器才会尝试自己去加载。
为什么要让父类加载器优先去加载呢?试想如果子类加载器先加载,那么我们可以写一些与java.lang包中基础类同名的类,然后再定义一个子类加载器,这样整个应用使用的基础类就都变成我们自己定义的类了。这样就有很大的安全隐患!所以自己编写类加载器时,如果没有特殊原因,一定要遵守类加载的双亲委派模型。
3Tomcat类加载器
Tomcat基本遵守了JVM的委派模型,但也在自定义的类加载器中做了细微的调整,以适应Tomcat自身的要求。下面是Tomcat类加载器WebappClassLoader的核心方法loadClass()的源码。它覆盖了父类URLClassLoader中的方法,改变了默认的类加载顺序。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
public
synchronized
Class loadClass(String name,
boolean
resolve)
throws
ClassNotFoundException {
Class clazz =
null
;
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if
(clazz !=
null
) {
if
(log.isDebugEnabled())
log.debug(
" Returning class from cache"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
// (0.1) Check our previously loaded class cache
clazz = findLoadedClass(name);
if
(clazz !=
null
) {
if
(log.isDebugEnabled())
log.debug(
" Returning class from cache"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding J2SE classes
try
{
clazz = system.loadClass(name);
if
(clazz !=
null
) {
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
// Ignore
}
boolean
delegateLoad = delegate || filter(name);
// (1) Delegate to our parent if requested
if
(delegateLoad) {
if
(log.isDebugEnabled())
log.debug(
" Delegating to parent classloader1 "
+ parent);
ClassLoader loader = parent;
if
(loader ==
null
)
loader = system;
try
{
clazz = loader.loadClass(name);
if
(clazz !=
null
) {
if
(log.isDebugEnabled())
log.debug(
" Loading class from parent"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
}
// (2) Search local repositories
if
(log.isDebugEnabled())
log.debug(
" Searching local repositories"
);
try
{
clazz = findClass(name);
if
(clazz !=
null
) {
if
(log.isDebugEnabled())
log.debug(
" Loading class from local repository"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
// (3) Delegate to parent unconditionally
if
(!delegateLoad) {
if
(log.isDebugEnabled())
log.debug(
" Delegating to parent classloader at end: "
+ parent);
ClassLoader loader = parent;
if
(loader ==
null
)
loader = system;
try
{
clazz = loader.loadClass(name);
if
(clazz !=
null
) {
if
(log.isDebugEnabled())
log.debug(
" Loading class from parent"
);
if
(resolve)
resolveClass(clazz);
return
(clazz);
}
}
catch
(ClassNotFoundException e) {
;
}
}
throw
new
ClassNotFoundException(name);
}
具体分析一下:首先findLoadedClass0()和findLoadedClass()分别从本地和父类加载器的缓存中查找当前要加载的类是否已经加载过了。之后为了避免上面提到的安全问题,Tomcat类加载器会将加载请求委派给系统类加载器。接下来根据delegate变量的设置,决定是先由自己加载,还是先由父类加载器去加载。
这里介绍一下背景,上面的WebappClassLoader是对应一个Web应用的类加载器,其父亲是Tomcat的lib的加载器。所以delegate变量的值,决定了Tomcat的类加载顺序。
4Tomcat6的加载顺序
所以在Tomcat 6中默认情况下,不是完全按照先Tomcat的lib再Web应用的lib这种顺序去加载类。Jar包的加载顺序是:1)JRE中的Java基础包2)Web应用WEB-INF\lib下的包3)Tomcat\lib下的包
如果想要在Web应用间共享一些Jar包,则不仅需要将公共包放在Tomcat的lib下,还要删掉Web应用lib中的包,否则Tomcat启动时还是会优先加载Web应用lib下的包的。
ps:题外话,如果想要自己指定一个Tomcat\lib和Web应用lib之外的ClassPath,除了修改Tomcat启动脚本外,可以为不同Web应用的Context指定一个VirtualWebappLoader,但源码注释中写到不推荐在生产环境中使用。
参考文章
《深入理解Java虚拟机》http://lovnet.iteye.com/blog/1825322