转载自http://blog.csdn.net/yuekun1172006/archive/2007/06/02/1634878.aspx
类装入器是 JVM 用来装入类的类,它对于 Java 编程是非常重要的一个概念。一般情况下,程序员在编写程序的时候都可以忽略类装入器的存在性。但是对于服务器端编程或者是一些特殊情况下时候,深入了解类装入器的机制以及其在不同情况下的实现还是非常必要的。
首先,当一个 JVM 启动的时候,Java 缺省开始使用三个类装入器。它们分别是:
- 引导(Bootstrap)类装入器;
- 扩展(Extension)类装入器;
- 系统(System)类装入器;
它们分别实现如下的功能:
- 引导类装入器是用本地代码实现的类装入器。它负责将
<Java_Runtime_Home>/lib
下面的类库加载到内存中。 - 扩展类装入器是由 Sun 的 ExtClassLoader 实现的。它负责将
< Java_Runtime_Home >/lib/ext
或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。 - 系统类装入器又叫应用程序类装入器,是由 Sun 的 AppClassLoader 实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。
当 应用程序需要加载某个类到内存中的时候,类装入器是如何工作的呢?这就设计到类装入器的一个重要方面:代理机制。每一个类装入器,除了引导类装入器以外, 都有一个父类装入器。对于系统缺省定义的三个类装入器,引导类装入器是扩展类装入器的父类装入器,而扩展类装入器是系统类装入器的父类装入器。当然,应用 程序也可以使用自己的类装入器来使用特定的方法来装载类,因此,整个系统中的类装入器就形成一个树状结构。
当使用某个类装入器来试图装载某个类的时候,该类装入器会首先使用其父类装入器来试图装载该类。对于每一个装载进来的类,JVM 都会给其分配一个唯一的 ID。因此,不同类装入器可以装载同一个类到 JVM 中。例如,对于如下图结构的 ClassLoaderA
和 ClassLoaderB
:
假设类 C
在系统类装入器指定的类路径中,则无论是使用 ClassLoaderA
还是使用 ClassLoaderB
,都只会得到同样一个类 C
。
但是如果类 C
分别在 ClassLoaderA
以及 ClassLoaderB
指定的类库中,则使用 ClassLoaderA
得到到类 C
实例会不同于 ClassLoaderB
得到的类 C
实例。尽管两个类装入器在同一个 JVM 中。
上面的类装入器的向上代理结构看上去很完美了,但是,当系统变得复杂的时候,就还是显得不够用了。
例 如,当 Java 引入了 JNDI 以后,JNDI 核心部分是通过引导 类装入器在 JVM 启动的时候装载进入 JVM 的。而 JDNI 核心部分是通过配置信息来在运行时候装载定义在用户的类路径中的特定类来完成特定需要。而这是上面定义的类装入器的向上代理模式所不能支持的。
为了解决这个问题,Java 2 中引入了线程上下文(Thread Content)类装入器的概念,每一个线程有一个 Context 类装入器。这个 Context 类装入器是通过方法 Thread.setContextClassLoader()
设置的,如果当前线程在创建后没有调用这个方法设置 Context 类装入器,则当前线程从他的父线程继承 Context 类装入器。如果整个应用都没有设置 Context 类装入器,则系统类装入器被设置为所有线程的 Context 类装入器。
对 于我们上面所说 JNDI 的情况,引导 类装入器装载进入的 JNDI 核心类会使用 Context 类装入器来装载其所需要的 JNDI 实现类,而不是将该装载任务代理给其父类装入器来完成。这样,就解决了上面的问题。可以认为 Context 类装入器在传统的 Java 向上代理机制上打开了一个后门。Context 类装入器在 J2EE 中使用的很广泛,比如 Java 命名服务(JNDI),Java API for XML Parsing(JAXP)(注:在 Java1.4 中 JAXP 才作为 Java 的核心类的一部分,它才开始使用 Context 类装入器来加载不同的实现类)等。
简单而言,Java 中的类装入器就是上面几种,但是,在具体使用中,还是有很多变化,我们下面分别对于一些情况进行说明。