首页 > 代码库 > 深入理解 java类加载器ClassLoader

深入理解 java类加载器ClassLoader

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象,也就是万能的Class对象。

Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:

  • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
下面这段代码的输出结果可以显示出classloader的树状组织结构。
public class ClassLoaderTest {
	public static void main(String[] args){
		 ClassLoader loader = ClassLoaderTest.class.getClassLoader(); 
	        while (loader != null) { 
	            System.out.println(loader.toString()); 
	            loader = loader.getParent(); 
	        } 
	}
}

其中loader,toString()方法的返回值等同于下面的表达式:

 getClass().getName() + ‘@‘ + Integer.toHexString(hashCode())
程序的输出如下:

sun.misc.Launcher$AppClassLoader@70a0afab
sun.misc.Launcher$ExtClassLoader@456d3d51

第一个输出的是 ClassLoaderTree类的类加载器,即系统类加载器。它是 sun.misc.Launcher$AppClassLoader类的实例;第二个输出的是扩展类加载器,是 sun.misc.Launcher$ExtClassLoader类的实例。需要注意的是这里并没有输出引导类加载器,这是由于有些 JDK 的实现对于父类加载器是引导类加载器的情况,getParent()方法返回 null

在了解了类加载器的树状组织结构之后,下面介绍类加载器的代理模式。

classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必 须重新启动JVM才能生效的原因。

每个ClassLoader加载Class的过程是:
1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4;如果存在,到3
3.请求parent classloader载入,如果成功到8,不成功到5
4.请求jvm从bootstrap classloader中载入,如果成功到8
5.寻找Class文件(从与此classloader相关的类路径中寻找)。找到了到6,如果找不到则到7.
6.从文件中载入Class,到8.
7.抛出ClassNotFoundException.
8.返回Class.

总结一下,类加载器的顺序是:
先是bootstrap classloader,然后是extension classloader,最后才是system classloader。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类, 也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:
   System.out.println(System.class.getClassLoader());
将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader不是一个真正的ClassLoader实例,而是由JVM实现的,正如前面已经说过的。


简而言之,就是自底向上检查类是否已经被加载,然后自顶向下尝试加载类。


值得一天的是tomcat的webappclassloader 这个相当于用户自定义加载器,为每个部署在独立tomcat实例上的web应用而创建。该加载器加载的类对于自身应用中的类都是可见的但对于其他web应用不可见,它负责加载下面路径中的类

/WEB-INF/classes
/WEB-INF/lib

和java2中的加载类的代理模式方式不同,webapp xx 类加载器采用的是另一种类加载模式(Servlet 2.4规范9.7.2节 web Application Classloader中建议使用这种方式),当一个request对象加载一个由webappxx Classloader负责加载的类时,webappxx Classloader 将首先在本地库(WEB-INF)进行搜索,与传统的委托给父加载器进行搜索的方式不同。