您的位置:首页 > 运维架构 > Tomcat

java与tomcat5类加载机制

2017-07-24 13:07 274 查看


1. java类加载器

近来了解tomcat的类加载机制,所以先回顾一下Java虚拟机类加载器,如果从java虚拟机的角度来看的话,其实类加载器只分为两种:一种是启动类加载器(即Bootstrap ClassLoader),通过使用JNI来实现,我们无法获取到到它的实例;另一种则是java语言实现
java.lang.ClassLoader
的子类。一般从我们的角度来看,会根据类加载路径会把类加载器分为3种:Bootstrap ClassLoader,ExtClassLoader,AppClassLoader.后两者是
sun.misc.Launcher
类的内部类,而前者在JDK源码中是没有与之对应的类的,倒是在
sun.misc.Launcher
中可以看到一些它的加载路径信息。如果找不到sun的源码,可以下载OpenJDK的来看一下。

Bootstrap ClassLoader: 引导类加载器,从%JAVA_RUNTIME_JRE%/lib目录加载,但并不是将该目录所有的类库都加载,它会加载一些符合文件名称的,例如:rt.jar,resources.jar等。在
sun.misc.Launcher
源码中也可以看得它的加载路径:
private static String bootClassPath = System.getProperty("sun.boot.class.path");
1
1

或者配置-Xbootclasspath参数指定加载的路径,通过获取环境变量
sun.boot.class.path
看一下到底具体加载了那些类:
D:\Program Files\Java\jdk1.7.0_67\jre\lib\resources.jar
D:\Program Files\Java\jdk1.7.0_67\jre\lib\rt.jar
D:\Program Files\Java\jdk1.7.0_67\jre\lib\sunrsasign.jar
D:\Program Files\Java\jdk1.7.0_67\jre\lib\jsse.jar
D:\Program Files\Java\jdk1.7.0_67\jre\lib\jce.jar
D:\Program Files\Java\jdk1.7.0_67\jre\lib\charsets.jar
D:\Program Files\Java\jdk1.7.0_67\jre\lib\jfr.jar
D:\Program Files\Java\jdk1.7.0_67\jre\classes
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8

Extension ClassLoader:扩展类加载器,实现类为
sun.misc.Launcher$ExtClassLoader
,加载%JAVA_RUNTIME_JRE%/lib/ext/目录下的jar包,也可以在
sun.misc.Launcher
源码中也可以看得它的加载路径:
String s = System.getProperty("java.ext.dirs");
1
1

通过获取
java.ext.dirs
环境变量打印一下:
D:\Program Files\Java\jdk1.7.0_67\jre\lib\ext
1
1

Appication ClassLoader:应用程序类加载器,或者叫系统类加载器,实现类为
sun.misc.Launcher$AppClassLoader
。从
sun.misc.Launcher
的构造函数中可以看到,当
AppClassLoader
被初始化以后,它会被设置为当前线程的上下文类加载器以及保存到
Launcher
类的loader属性中,而通过
ClassLoader.getSystemClassLoader()
获取的也正是该类加载器(Launcher.loader)。应用类加载器从用户类路径中加载类库,可以在源码中看到:
final String s = System.getProperty("java.class.path");
1
1


1.1 类关系



由图看到Bootstrap ClassLoader并不在继承链上,因为它是java虚拟机内置的类加载器,对外不可见。可以看到顶层
ClassLoader
有一个parent属性,用来表示着类加载器之间的层次关系(双亲委派模型);注意,
ExtClassLoader
类在初始化时显式指定了parent为null,所以它的父类加载器默认为
Bootstrap
ClassLoader
。在tomcat中都是通过扩展
URLClassLoader
来实现自己的类加载器。


1.2 双亲委托模型

这3种类加载器之间存在着父子关系(区别于java里的继承),子加载器保存着父加载器的引用。当一个类加载器需要加载一个目标类时,会先委托父加载器去加载,然后父加载器会在自己的加载路径中搜索目标类,父加载器在自己的加载范围中找不到时,才会交还给子加载器加载目标类。

采用双亲委托模式可以避免类加载混乱,而且还将类分层次了,例如java中lang包下的类在jvm启动时就被启动类加载器加载了,而用户一些代码类则由应用程序类加载器(AppClassLoader)加载,基于双亲委托模式,就算用户定义了与lang包中一样的类,最终还是由应用程序类加载器委托给启动类加载器去加载,这个时候启动类加载器发现已经加载过了lang包下的类了,所以两者都不会再重新加载。当然,如果使用者通过自定义的类加载器可以强行打破这种双亲委托模型,但也不会成功的,java安全管理器抛出将会抛出
java.lang.SecurityException
异常。



启动类加载器是扩展类加载器的父类加载器:扩展类加载器在
sun.misc.Launcher
构造函数中被初始化,它的父类加载器被设置了为null,那为什么还说启动类加载器是它的父加载器?看一下
ClassLoader.loadClass()
方法:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,查找该类是否已经被加载过了
Class c = findLoadedClass(name);
if (c == null) {  //未被加载过
long t0 = System.nanoTime();
try {
if (parent != null) {  // 父类加载器不为null,则调用父类加载器尝试加载
c = parent.loadClass(name, false);
} else {   // 父类加载器为null,则调用本地方法,交由启动类加载器加载,所以说ExtClassLoader的父类加载器为Bootstrap ClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) { //仍然加载不到,只能由本加载器通过findClass去加载
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

从代码中看到,如果parent==null,将会由启动类加载器尝试加载,所以扩展类加载器的父类加载器是启动类加载器。
扩展类加载器是应用程序类加载器的父类加载器:这个比较好理解,依然是在
sun.misc.Launcher
构造函数初始化应用程序类加载器时,指定了ExtClassLoader为AppClassLoader的父类加载器:
loader = AppClassLoader.getAppClassLoader(extcl);//loader是ClassLoader的属性,extcl是扩展类加载器实例
1
1
应用程序类加载器是自定义类加载器的父类加载器:这里指的是使用默认构造函数进行自定义类加载器(否则 你可以指定parent来构造一个父加载器为ExtClassLoader的自定义类加载器),无论是通过扩展ClassLoader还是URLClassLoader最终都会获取系统类加载器(AppClassLoader)作为父类加载器:
protected ClassLoader() {
//调用getSystemClassLoader方法获取系统类加载器作为父类加载器
this(checkCreateClassLoader(), getSystemClassLoader());
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader(); //初始化系统类加载器
.....
return scl;
}
private static synchronized void initSystemClassLoader() {
......
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
......
scl = l.getClassLoader();  //这里拿到的就是在Launcher构造函数中构造的AppClassLoader实例
......
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


2. tomcat7类加载器

tomcat作为一个Java web容器,也有自己的类加载机制,通过自定义的类加载机制以实现共享类库的抽取,不同web应用之间的资源隔离还有热加载等功能。除了一些java自身的一些类加载器处,它实现的主要类加载器有:Common
ClassLoader,Catalina ClassLoader,Shared ClassLoader以及WebApp ClassLoader.通过下面类关系图以及逻辑关系图,同时对比上文内容梳理这些类加载器之间的关系。


2.1 类关系图



从图中看到了Common,Catalina,Shared类加载器是URLClassLoader类的一个实例,只是它们的类加载路径不一样,在tomcat/conf/catalina.properties配置文件中配置(common.loader,server.loader,shared.loader).WebAppClassLoader继承自WebAppClassLoaderBase,基本所有逻辑都在WebAppClassLoaderBase为中实现了,可以看出tomcat的所有类加载器都是以URLClassLoader为基础进行扩展。


2.2 逻辑关系图



上面说到Common,Catalina,Shared类加载器是URLClassLoader类的一个实例,在默认的配置中,它们其实都是同一个对象,即commonLoader,结合初始化时的代码(只保留关键代码):
private void initClassLoaders() {
commonLoader = createClassLoader("common", null);  // commonLoader的加载路径为common.loader
if( commonLoader == null ) {
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader); // 加载路径为server.loader,默认为空,父类加载器为commonLoader
sharedLoader = createClassLoader("shared", commonLoader); // 加载路径为shared.loader,默认为空,父类加载器为commonLoader
}
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;      // catalinaLoader与sharedLoader的加载路径均为空,所以直接返回commonLoader对象,默认3者为同一个对象
}
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13

在上面的代码初始化时很明确是指出了,catalina与shared类加载器的父类加载器为common类加载器,而初始化commonClassLoader时父类加载器设置为null,最终会调到
createClassLoader
静态方法:
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {
.....
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);  //该构造方法默认获取系统类加载器为父类加载器,即AppClassLoader
else
return new URLClassLoader(array, parent);
}
});

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

createClassLoader
中指定参数
parent==null
时,最终会以系统类加载器(AppClassLoader)作为父类加载器,这解释了为什么commonClassLoader的父类加载器是AppClassLoader.

一个web应用对应着一个
StandardContext
实例,每个web应用都拥有独立web应用类加载器(WebClassLoader),这个类加载器在
StandardContext.startInternal()
中被构造了出来:
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
1
2
3
4
5
1
2
3
4
5

这里
getParentClassLoader()
会获取父容器
StandarHost.parentClassLoader
对象属性,而这个对象属性是在
Catalina$SetParentClassLoaderRule.begin()
初始化,初始化的值其实就是
Catalina.parentClassLoader
对象属性,再来跟踪一下
Catalina.parentClassLoader
,在
Bootstrap.init()
时通过反射调用了
Catalina.setParentClassLoader()
,将
Bootstrap.sharedLoader
属性设置为
Catalina.parentClassLoader
,所以WebClassLoader的父类加载器是Shared
ClassLoader.


2.3 类加载逻辑

tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。具体的加载逻辑位于W
ebAppClassLoaderBase.loadClass()
方法中,代码篇幅长,这里以文字描述加载一个类过程:
先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在
resourceEntries
这个数据结构中),如果已经加载即返回,否则 继续下一步。
让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖,如果加载到即返回,返回继续。
前两步均没加载到目标类,那么web应用的类加载器将自行加载,如果加载到则返回,否则继续下一步。
最后还是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。

第3第4两个步骤的顺序已经违反了双亲委托机制,除了tomcat之外,JDBC,JNDI,
Thread.currentThread().setContextClassLoader();
等很多地方都一样是违反了双亲委托。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: