最近几天突然想把Java类加载器的原理搞清楚,于是在网上搜了一些资料,以下是我学习资料后的一点体会。
首先,标准的Java类加载器是有继承关系的树形结构。当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:
bootstrap classloader
|
extension classloader
|
system classloader
|
extension classloader
|
system classloader
bootstrap classloader:引导类加载器,它负责加载Java的核心类。在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用- D选项指定sun.boot.class.path系统属性值可以指定附加的类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。大家可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
public static void main(String args[]) {
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL u : urls) {
System.out.println(u.toExternalForm());
}
}
在我的计算机上(IBM Webshpere自带的IBM的JVM)的结果为:
file:/C:/WASX/java/jre/lib/vm.jar
file:/C:/WASX/java/jre/lib/core.jar
file:/C:/WASX/java/jre/lib/charsets.jar
file:/C:/WASX/java/jre/lib/graphics.jar
file:/C:/WASX/java/jre/lib/security.jar
file:/C:/WASX/java/jre/lib/ibmpkcs.jar
file:/C:/WASX/java/jre/lib/ibmorb.jar
file:/C:/WASX/java/jre/lib/ibmcfw.jar
file:/C:/WASX/java/jre/lib/ibmorbapi.jar
file:/C:/WASX/java/jre/lib/ibmjcefw.jar
file:/C:/WASX/java/jre/lib/ibmjgssprovider.jar
file:/C:/WASX/java/jre/lib/ibmjsseprovider2.jar
file:/C:/WASX/java/jre/lib/ibmjaaslm.jar
file:/C:/WASX/java/jre/lib/ibmjaasactivelm.jar
file:/C:/WASX/java/jre/lib/ibmcertpathprovider.jar
file:/C:/WASX/java/jre/lib/server.jar
file:/C:/WASX/java/jre/lib/xml.jar
这时大家知道了为什么我们不需要在系统属性CLASSPATH中指定这些类库了吧,因为JVM在启动的时候就自动加载它们了。
public static void main(String args[]) {
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (URL u : urls) {
System.out.println(u.toExternalForm());
}
}
在我的计算机上(IBM Webshpere自带的IBM的JVM)的结果为:
file:/C:/WASX/java/jre/lib/vm.jar
file:/C:/WASX/java/jre/lib/core.jar
file:/C:/WASX/java/jre/lib/charsets.jar
file:/C:/WASX/java/jre/lib/graphics.jar
file:/C:/WASX/java/jre/lib/security.jar
file:/C:/WASX/java/jre/lib/ibmpkcs.jar
file:/C:/WASX/java/jre/lib/ibmorb.jar
file:/C:/WASX/java/jre/lib/ibmcfw.jar
file:/C:/WASX/java/jre/lib/ibmorbapi.jar
file:/C:/WASX/java/jre/lib/ibmjcefw.jar
file:/C:/WASX/java/jre/lib/ibmjgssprovider.jar
file:/C:/WASX/java/jre/lib/ibmjsseprovider2.jar
file:/C:/WASX/java/jre/lib/ibmjaaslm.jar
file:/C:/WASX/java/jre/lib/ibmjaasactivelm.jar
file:/C:/WASX/java/jre/lib/ibmcertpathprovider.jar
file:/C:/WASX/java/jre/lib/server.jar
file:/C:/WASX/java/jre/lib/xml.jar
这时大家知道了为什么我们不需要在系统属性CLASSPATH中指定这些类库了吧,因为JVM在启动的时候就自动加载它们了。
extension classloader:扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的目录)中JAR的类包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。所以当大家执行以下代码时:
System.out.println(System.getProperty("java.ext.dirs"));
ClassLoader extensionClassloader = ClassLoader.getSystemClassLoader().getParent();
System.out.println("the parent of extension classloader : " + extensionClassloader.getParent());
结果为:
c:/wasx/java/jre/lib/ext
the parent of extension classloader : null
extension classloader是system classloader的parent,而bootstrap classloader是extension classloader的parent,但它不是一个实际的classloader,所以为null。
System.out.println(System.getProperty("java.ext.dirs"));
ClassLoader extensionClassloader = ClassLoader.getSystemClassLoader().getParent();
System.out.println("the parent of extension classloader : " + extensionClassloader.getParent());
结果为:
c:/wasx/java/jre/lib/ext
the parent of extension classloader : null
extension classloader是system classloader的parent,而bootstrap classloader是extension classloader的parent,但它不是一个实际的classloader,所以为null。
system classloader:系统(也称为应用)类加载器,是我们接触最多,也是最常用的类加载器。它负责在JVM启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。总能通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。执行以下代码即可获得:
System.out.println(System.getProperty("java.class.path"));
输出结果则为用户在系统属性里面设置的CLASSPATH。
classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。
System.out.println(System.getProperty("java.class.path"));
输出结果则为用户在系统属性里面设置的CLASSPATH。
classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。
以下是Sun的java5.0中ClassLoader.java的部分源码,从中可以看出每个ClassLoader加载Class的过程。
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//检测此Class是否载入过(即在cache中是否有此Class)
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//如果parent classloader存在,请求parent classloader载入
c = parent.loadClass(name, false);
} else {
//parent classloader不存在,那parent一定是bootstrap classloader了,
//请求jvm从bootstrap classloader中载入
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
//如果还没找到,调用自己的findClass方法寻找Class文件(从与此classloader相关的类路径中寻找)
c = findClass(name);
//如果这都没找到,就只能抛ClassNotFoundException了
}
}
if (resolve) {
resolveClass(c);
}
return c; //返回Class
}
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//检测此Class是否载入过(即在cache中是否有此Class)
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//如果parent classloader存在,请求parent classloader载入
c = parent.loadClass(name, false);
} else {
//parent classloader不存在,那parent一定是bootstrap classloader了,
//请求jvm从bootstrap classloader中载入
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
//如果还没找到,调用自己的findClass方法寻找Class文件(从与此classloader相关的类路径中寻找)
c = findClass(name);
//如果这都没找到,就只能抛ClassNotFoundException了
}
}
if (resolve) {
resolveClass(c);
}
return c; //返回Class
}
我们可以通过覆盖ClassLoader的findClass方法来实现自己的载入策略。甚至覆盖loadClass方法来实现自己的载入过程。OSGI就是通过覆盖了loadClass方法,使他们的classloader都先从他们所依赖的多个classloader中寻找Class,从而打破了java固有的单一继承树状关系的classloader体系。因此OSGI的classloader不仅仅是一个树状的继承关系,而是一个网状的依赖(dependency)关系(当然,没有依赖回路,即没有c1->c2->c3->c1的路径)。
类加载器的顺序是:
先是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类,也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:
System.out.println(System.class.getClassLoader());
将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader不是一个真正的ClassLoader实例,而是由JVM实现的,正如前面已经说过的。
先是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类,也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:
System.out.println(System.class.getClassLoader());
将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader不是一个真正的ClassLoader实例,而是由JVM实现的,正如前面已经说过的。
下面就让我们来看看JVM是如何来为我们来建立类加载器的结构的:
sun.misc.Launcher,顾名思义,当你执行java命令的时候,JVM会先使用bootstrap classloader载入并初始化一个Launcher,执行下列代码:
System.out.println("the Launcher's classloader is " + sun.misc.Launcher.getLauncher().getClass().getClassLoader());
结果为:
the Launcher's classloader is null (因为是用bootstrap classloader加载,所以class loader为null)
Launcher 会根据系统和命令设定初始化好class loader结构,JVM就用它来获得extension classloader和system classloader,并载入所有的需要载入的Class,最后执行java命令指定的带有静态的main方法的Class。extension classloader实际上是sun.misc.Launcher$ExtClassLoader类的一个实例,system classloader实际上是sun.misc.Launcher$AppClassLoader类的一个实例。并且都是 java.net.URLClassLoader的子类。
sun.misc.Launcher,顾名思义,当你执行java命令的时候,JVM会先使用bootstrap classloader载入并初始化一个Launcher,执行下列代码:
System.out.println("the Launcher's classloader is " + sun.misc.Launcher.getLauncher().getClass().getClassLoader());
结果为:
the Launcher's classloader is null (因为是用bootstrap classloader加载,所以class loader为null)
Launcher 会根据系统和命令设定初始化好class loader结构,JVM就用它来获得extension classloader和system classloader,并载入所有的需要载入的Class,最后执行java命令指定的带有静态的main方法的Class。extension classloader实际上是sun.misc.Launcher$ExtClassLoader类的一个实例,system classloader实际上是sun.misc.Launcher$AppClassLoader类的一个实例。并且都是 java.net.URLClassLoader的子类。
让我们来看看Launcher初试化的过程的部分代码。
Launcher的部分代码:
public class Launcher {
public Launcher() {
ExtClassLoader extclassloader;
try {
//初始化extension classloader
extclassloader = ExtClassLoader.getExtClassLoader();
} catch(IOException ioexception) {
throw new InternalError("Could not create extension class loader");
}
try {
//初始化system classloader,parent是extension classloader
loader = AppClassLoader.getAppClassLoader(extclassloader);
} catch(IOException ioexception1) {
throw new InternalError("Could not create application class loader");
}
//将system classloader设置成当前线程的context classloader(将在后面加以介绍)
Thread.currentThread().setContextClassLoader(loader);
......
}
public ClassLoader getClassLoader() {
//返回system classloader
return loader;
}
}
public class Launcher {
public Launcher() {
ExtClassLoader extclassloader;
try {
//初始化extension classloader
extclassloader = ExtClassLoader.getExtClassLoader();
} catch(IOException ioexception) {
throw new InternalError("Could not create extension class loader");
}
try {
//初始化system classloader,parent是extension classloader
loader = AppClassLoader.getAppClassLoader(extclassloader);
} catch(IOException ioexception1) {
throw new InternalError("Could not create application class loader");
}
//将system classloader设置成当前线程的context classloader(将在后面加以介绍)
Thread.currentThread().setContextClassLoader(loader);
......
}
public ClassLoader getClassLoader() {
//返回system classloader
return loader;
}
}
extension classloader的部分代码:
static class Launcher$ExtClassLoader extends URLClassLoader {
static class Launcher$ExtClassLoader extends URLClassLoader {
public static Launcher$ExtClassLoader getExtClassLoader()
throws IOException
{
File afile[] = getExtDirs();
return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
}
private static File[] getExtDirs() {
//获得系统属性"java.ext.dirs"
String s = System.getProperty("java.ext.dirs");
File afile[];
if(s != null) {
StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
int i = stringtokenizer.countTokens();
afile = new File;
for(int j = 0; j < i; j++)
afile[j] = new File(stringtokenizer.nextToken());
throws IOException
{
File afile[] = getExtDirs();
return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
}
private static File[] getExtDirs() {
//获得系统属性"java.ext.dirs"
String s = System.getProperty("java.ext.dirs");
File afile[];
if(s != null) {
StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
int i = stringtokenizer.countTokens();
afile = new File;
for(int j = 0; j < i; j++)
afile[j] = new File(stringtokenizer.nextToken());
} else {
afile = new File[0];
}
return afile;
}
}
afile = new File[0];
}
return afile;
}
}
system classloader的部分代码:
static class Launcher$AppClassLoader extends URLClassLoader
{
static class Launcher$AppClassLoader extends URLClassLoader
{
public static ClassLoader getAppClassLoader(ClassLoader classloader)
throws IOException
{
//获得系统属性“java.class.path”
String s = System.getProperty("java.class.path");
File afile[] = s != null ? Launcher.access$200(s) : new File[0];
return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
}
}
throws IOException
{
//获得系统属性“java.class.path”
String s = System.getProperty("java.class.path");
File afile[] = s != null ? Launcher.access$200(s) : new File[0];
return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
}
}
看了源代码大家就清楚了吧,extension classloader是使用系统属性“java.ext.dirs”设置类搜索路径的,并且没有parent。system classloader是使用系统属性“java.class.path”设置类搜索路径的,并且有一个parent classloader。Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。
这里怎么又出来一个context classloader呢?它有什么用呢?我们在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,子classloader可以获得当前线程的context classloader,而这个context classloader可以是它的父classloader或者其他的classloader,那么子classloader就可以从其获得所需的Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例如web应用中的servlet就是用这种机制加载的。