谈谈Java虚拟机2――类加载器 (二)

2014-11-24 03:19:46 · 作者: · 浏览: 3

  • 类加载器的树状组织结构

在系统提供的类加载器中,除了启动类加载器之外,所有的类加载器都有一个父类加载器。通过getParent()方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是启动类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。

\

下面代码演示了类加载器的树状组织结构

public class ClassloaderTree {

/**

* @param args

*/

public static void main(String[] args) {

ClassLoader loader = ClassloaderTree.class.getClassLoader();

while(loader!=null){

System.out.println(loader);

loader = loader.getParent();

}

}

}

打印出来的结果为:

sun.misc.Launcher$AppClassLoader@1372a1a

sun.misc.Launcher$ExtClassLoader@ad3ba4

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

代理模式

代理模式说的是双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围没有找到所需的类)时,子加载器才会尝试自己去加载。

那么为什么要使用代理模式呢,每个类加载器都由自己加载不是很好吗,搞得这么复杂干吗?要解释这个,就得首先说明Java虚拟机是如何判定两个类是相同的。在Java虚拟机中,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,通俗来说,Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。这个说的相同,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等情况。

下面我们通过编写自定义的类加载器来分别加载同一个class文件,进而确定得到的类是否相同。

在 上表中列出的java.lang.ClassLoader类的常用方法中,一般来说,自己开发的类加载器只需要覆写findClass(String name)方法即可。java.lang.ClassLoader类的方法loadClass()封装了前面提到的代理模式的实现(loadClass的具体实现如下所示)。该方法会首先调用findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写loadClass()方法,而是覆写findClass()方法。

protected synchronized Class< > loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

// First, check if the class has already been loaded

Class c = findLoadedClass(name);

if (c == null) {

try {

if (parent != null) {

c = parent.loadClass(name, false);

} else {

c = findBootstrapClass0(name);

}

} catch (ClassNotFoundException e) {

// If still not found, then invoke findClass in order

// to find the class.

c = findClass(name);

}

}

if (resolve) {

resolveClass(c);

}

return c;

}

在本例中,我们为了说明两个类加载器加载同一个class会得到不同的类,所以重写loadClass方法,如果只重写findClass方法,会由双亲来加载class文件,得不到想到的效果。

自定义的ClassLoader代码如下:

import java.io.InputStream;

public class MyClassLoader extends ClassLoader {

@Override

public Class< > loadClass(String name) throws ClassNotFoundException {

// TODO Auto-generated method stub

String fileName = name.substring(name.lastIndexOf(".")+1)+".class";

InputStream is = getClass().getResourceAsStream(fileName);

if( is == null){

return super.loadClass(name);

}

try {

byte[] b = new byte[is.available()];

is.read(b);

return defineClass(name,b,0