`

classloader学习

    博客分类:
  • java
 
阅读更多

 

个人总结:

  $1.classloader是用来加载java类到jvm中的,有四种classloader,bootstrape class loader是加载核心库的如rt.jar等,Extension ClassLoader是用来加载扩展类,即/lib/ext中的类。
AppClassLoader用来加载Classpath的类,是和我们关系最密切的类。
URLClassLoader用来加载网络上远程的类

$2.可以隐式加载比如我们new一个对象时,它会隐式把该类加载进来,我们也可以同过class.forName()方法指定classloader来加载。

$3.先由parent加载,如果不行再自己加载,因为是树形结构,所以这里所说的父类实际是父节点。

 

##1。转自: http://sys53.iteye.com/blog/622626

ClassLoader的工作原理

每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类
系统默认的contextClassLoader是systemClassLoader,所以一般而言java程序在执行时可以使用JVM自带的类、$JAVA_HOME/jre/lib/ext/中的类和$CLASSPATH/中的类
可以使用Thread.currentThread().setContextClassLoader(...);更改当前线程的contextClassLoader,来改变其载入类的行为

ClassLoader被组织成树形,一般的工作原理是:
1) 线程需要用到某个类,于是contextClassLoader被请求来载入该类
2) contextClassLoader请求它的父ClassLoader来完成该载入请求
3) 如果父ClassLoader无法载入类,则contextClassLoader试图自己来载入

Java中一共有四个类加载器,之所以叫类加载器,是程序要用到某个类的时候,要用类加载器载入内存。
这四个类加载器分别为:Bootstrap ClassLoader、Extension ClassLoader、AppClassLoader
和URLClassLoader,他们的作用其实从名字就可以大概推测出来了。其中AppClassLoader在很多地方被叫做System ClassLoader

Bootstrap ClassLoader是在JVM开始运行的时候加载java的核心类,是用C++编写的,它用来加载核心类库,在JVM源代码中这样写道:
static const char classpathFormat[] =
"%/lib/rt.jar:"
"%/lib/i18n.jar:"
"%/lib/sunrsasign.jar:"
"%/lib/jsse.jar:"
"%/lib/jce.jar:"
"%/lib/charsets.jar:"
"%/classes";
Extension ClassLoader是用来加载扩展类,即/lib/ext中的类。
AppClassLoader用来加载Classpath的类,是和我们关系最密切的类。
URLClassLoader用来加载网络上远程的类

1. 预先加载与依需求加载

Java 运行环境为了优化系统,提高程序的执行速度,在 JRE 运行的开始会将 Java 运行所需要的基本类采用预先加载( pre-loading )的方法全部加载要内存当中,因为这些单元在 Java 程序运行的过程当中经常要使用的,主要包括 JRE 的 rt.jar 文件里面所有的 .class 文件。

当 java.exe 虚拟机开始运行以后,它会找到安装在机器上的 JRE 环境,然后把控制权交给 JRE , JRE 的类加载器会将 lib 目录下的 rt.jar 基础类别文件库加载进内存,这些文件是 Java 程序执行所必须的,所以系统在开始就将这些文件加载,避免以后的多次 IO 操作,从而提高程序执行效率。

相对于预先加载,我们在程序中需要使用自己定义的类的时候就要使用依需求加载方法( load-on-demand ),就是在 Java 程序需要用到的时候再加载,以减少内存的消耗,因为 Java 语言的设计初衷就是面向嵌入式领域的。

2. 隐式加载和显示加载

Java 的加载方式分为隐式加载( implicit )和显示加载( explicit ),上面的例子中就是用的隐式加载的方式。所谓隐式加载就是我们在程序中用 new 关键字来定义一个实例变量, JRE 在执行到 new 关键字的时候就会把对应的实例类加载进入内存。隐式加载的方法很常见,用的也很多, JRE 系统在后台自动的帮助用户加载,减少了用户的工作量,也增加了系统的安全性和程序的可读性。

Java代码 复制代码 收藏代码
  1. Class c = Class.forName("TestClass");
  2. TestClass object = (TestClass)c.newInstance();
  3. object.method();
 Class c = Class.forName("TestClass"); 

  TestClass object = (TestClass)c.newInstance(); 

 object.method(); 


通过 Class 类的 forName (String s) 方法把自定义类 TestClass 加载进来,并通过 newInstance ()方法把实例初始化。事实上 Class 类还很多的功能,这里就不细讲了,有兴趣的可以参考 JDK 文档。

Class 的 forName() 方法还有另外一种形式: Class forName(String s, boolean flag, ClassLoader classloader) , s 表示需要加载类的名称, flag 表示在调用该函数加载类的时候是否初始化静态区, classloader 表示加载该类所需的加载器。

forName (String s) 是默认通过 ClassLoader.getCallerClassLoader() 调用类加载器的,但是该方法是私有方法,我们无法调用,如果我们想使用 Class forName(String s, boolean flag, ClassLoader classloader) 来加载类的话,就必须要指定类加载器,可以通过如下的方式来实现:

Test test = new Test();//Test 类为自定义的一个测试类;

ClassLoader cl = test. getClass().getClassLoader();

// 获取 test 的类装载器;

Class c = Class.forName("TestClass", true, cl);

因为一个类要加载就必需要有加载器,这里我们是通过获取加载 Test 类的加载器 cl 当作加载 TestClass 的类加载器来实现加载的。
3. 自定义类加载机制

之前我们都是调用系统的类加载器来实现加载的,其实我们是可以自己定义类加载器的。利用 Java 提供的 java.net.URLClassLoader 类就可以实现。下面我们看一段范例:

try{

URL url = new URL("file:/d:/test/lib/");

URLClassLoader urlCL = new URLClassLoader(new URL[]{url});

Class c = urlCL.loadClass("TestClassA");

TestClassA object = (TestClassA)c.newInstance();

object.method();

}catch(Exception e){

e.printStackTrace();

}

我们通过自定义的类加载器实现了 TestClassA 类的加载并调用 method ()方法。分析一下这个程序:首先定义 URL 指定类加载器从何处加载类, URL 可以指向网际网络上的任何位置,也可以指向我们计算机里的文件系统 ( 包含 JAR 文件 ) 。上述范例当中我们从 file:/d:/test/lib/ 处寻找类;然后定义 URLClassLoader 来加载所需的类,最后即可使用该实例了。

4. 类加载器的阶层体系

讨论了这么多以后,接下来我们仔细研究一下 Java 的类加载器的工作原理:

当执行 java ***.class 的时候, java.exe 会帮助我们找到 JRE ,接着找到位于 JRE 内部的 jvm.dll ,这才是真正的 Java 虚拟机器 , 最后加载动态库,激活 Java 虚拟机器。虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类加载器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰写而成,这个 Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader ,并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader ,并设定其 Parent 为之前产生的 ExtClassLoader 实体。这两个加载器都是以静态类的形式存在的。这里要请大家注意的是, Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。
这三个加载器就构成我们的 Java 类加载体系。他们分别从以下的路径寻找程序所需要的类:

BootstrapLoader : sun.boot.class.path

ExtClassLoader: java.ext.dirs

AppClassLoader: java.class.path

这三个系统参量可以通过 System.getProperty() 函数得到具体对应的路径。

 

##2.http://longdick.iteye.com/blog/442213

java应用环境中不同的class分别由不同的ClassLoader负责加载。
一个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader,分别各司其职:

 

    • Bootstrap ClassLoader 负责加载java基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等
    • Extension ClassLoader 负责加载java扩展类,主要是 %JRE_HOME/lib/ext 目录下的jar和class
    • App ClassLoader 负责加载当前java应用的classpath中的所有类。

 

 

其中Bootstrap ClassLoader是JVM级别的,由C++撰写;Extension ClassLoader、App ClassLoader都是java类,都继承自URLClassLoader超类。
Bootstrap ClassLoader由JVM启动,然后初始化sun.misc.Launcher ,sun.misc.Launcher初始化Extension ClassLoader、App ClassLoader。

下图是ClassLoader的加载类流程图,以加载一个类的过程类示例说明整个ClassLoader的过程。

 



Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader三者的关系如下:

Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent。

但是这并不是继承关系,只是语义上的定义,基本上,每一个ClassLoader实现,都有一个Parent ClassLoader。

 

可以通过ClassLoader的getParent方法得到当前ClassLoader的parent。Bootstrap ClassLoader比较特殊,因为它不是java class所以Extension ClassLoader的getParent方法返回的是NULL。

 

了解了ClassLoader的原理和流程以后,我们可以试试自定义ClassLoader。

 

关于自定义ClassLoader:

 

由于一些特殊的需求,我们可能需要定制ClassLoader的加载行为,这时候就需要自定义ClassLoader了.

自定义ClassLoader需要继承ClassLoader抽象类,重写findClass方法,这个方法定义了ClassLoader查找class的方式。

主要可以扩展的方法有:

findClass 定义查找Class的方式

defineClass 将类文件字节码加载为jvm中的class

findResource 定义查找资源的方式

 

如果嫌麻烦的话,我们可以直接使用或继承已有的ClassLoader实现,比如

 

  • java.net.URLClassLoader
  • java.security.SecureClassLoader
  • java.rmi.server.RMIClassLoader
  • sun.applet.AppletClassLoader

 

Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子类。

这个是URLClassLoader的构造方法:

 

public URLClassLoader(URL[] urls, ClassLoader parent)

public URLClassLoader(URL[] urls)

 

urls参数是需要加载的ClassPath url数组,可以指定parent ClassLoader,不指定的话默认以当前调用类的ClassLoader为parent。

 

代码示例:

 

Java代码 复制代码 收藏代码
  1. ClassLoader classLoader = new URLClassLoader(urls);
  2. Thread.currentThread().setContextClassLoader(classLoader);
  3. Class clazz=classLoader.loadClass("com.company.MyClass");//使用loadClass方法加载class,这个class是在urls参数指定的classpath下边。
  4. Method taskMethod = clazz.getMethod("doTask", String.class, String.class);//然后我们就可以用反射做些事情了
  5. taskMethod.invoke(clazz.newInstance(),"hello","world");
ClassLoader classLoader = new URLClassLoader(urls);
Thread.currentThread().setContextClassLoader(classLoader);
Class clazz=classLoader.loadClass("com.company.MyClass");//使用loadClass方法加载class,这个class是在urls参数指定的classpath下边。

Method taskMethod = clazz.getMethod("doTask", String.class, String.class);//然后我们就可以用反射做些事情了
taskMethod.invoke(clazz.newInstance(),"hello","world");

 

 

由于classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入。

所以,当我们自定义的classloader加载成功了com.company.MyClass以后,MyClass里所有依赖的class都由这个classLoader来加载完成。

 

自定义ClassLoader在某些应用场景还是比较适用,特别是需要灵活地动态加载class的时候。

下面这篇文章列出了其中一种自定义ClassLoader的应用场景,有兴趣的同学可以参考下:

http://longdick.iteye.com/blog/332580

 

##3。转自: http://blog.csdn.net/java2000_wl/article/details/8222876

 

Java虚拟机学习 - 类加载器(ClassLoader)

 

类加载器

类加载器(ClassLoader)用来加载 class字节码到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类方式如下:Java 源文件在经过 Javac之后就被转换成 Java 字节码文件(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class一个实例。每一个这样实例用来表示一个 Java 类。实际情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成,也可能是通过网络下载。

 

类与类加载器

类加载器虽然只用于实现类加载动作,但它在Java程序中起到作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它类加载器这个类本身一同确立其在Java虚拟中唯一性。说通俗一些,比较两个类是否“相等”,只有在两个类是由同一个类加载器前提之下才有意义,否则,即使这两个类来源于同一个class文件,只要加载它类加载器不同,那这两个类必定不相等。这里所指“相等”包括代表类Class对象equal方法、isAssignableFrom()、isInstance()方法及instance关键字返回结果。

类加载器分类:

 

 

主要分为Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoaderUser Defined ClassLoader。

启动类加载器(Bootstrap ClassLoader):

这个类加载器使用C++语言实现,并非ClassLoader子类。主要负责加载存放在JAVA_HOME / jre / lib / rt.jar里面所有class文件,或者被-Xbootclasspath参数所指定路径中以rt.jar命名文件。

扩展类加载器(Extension ClassLoader):

这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载AVA_HOME / lib / ext目录中,或者被java.ext.dirs系统变量所指定路径中所有类库。

应用程序类加载器(Application ClassLoader):

这个加载器由sun.misc.Launcher$AppClassLoader实现,它负责加载classpath对应jar及目录。一般情况下这个就是程序中默认类加载器。

自定义类加载器(User Defined ClassLoader):

开发人员继承ClassLoader抽象类自行实现类加载器,基于自行开发ClassLoader可用于并非加载classpath中(例如从网络上下载jar或二进制字节码)、还可以在加载class文件之前做些小动作 如:加密等。

双亲委托模型:

上图中所展示类加载器之间这种层次关系,就称为类加载器双亲委托模型。双亲委托模型要求除了顶层启动类加载器外,其余类加载器都应当有自己父类加载器。这里类加载器之间父子关系一般不会以继承关系来实现,而是使用组合关系来复用父加载器代码。

 

public abstract class ClassLoader {

    private static native void registerNatives();
    static {
        registerNatives();
    }

    // The parent class loader for delegation
    private ClassLoader parent;

    // Hashtable that maps packages to certs
    private Hashtable package2certs = new Hashtable(11);
}

 

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

使用双亲委托模型来组织类加载器之间关系,有一个显而易见好处就是Java类随着它类加载器一起具备了一种带有优先级层次关系,例如java.lang.Object存放在rt.jar之中,无论那个类加载器要加载这个类,最终都是委托给启动类加载器进行加载,因此Object类在程序各种类加载器环境中都是同一个类,相反,如果没有双亲委托模型,由各个类加载器去完成话,如果用户自己写一个名为java.lang.Object类,并放在classpath中,应用程序中可能会出现多个不同Object类,java类型体系中最基本安全行为也就无法保证。

类加载器SPI:

java.lang.ClassLoader 类提供几个关键方法:

loadClass: 此方法负责加载指定名字类,首先会从已加载类中去寻找,如果没有找到;从parent ClassLoader[ExtClassLoader]中加载;如果没有加载到,则从Bootstrap ClassLoader中尝试加载(findBootstrapClassOrNull方法), 如果还是加载失败,则抛出异常ClassNotFoundException, 在调用自己findClass方法进行加载。如果要改变类加载顺序可以覆盖此方法;如果加载顺序相同,则可以通过覆盖findClass方法来做特殊处理,例如:解密,固定路径寻找等。当通过整个寻找类过程仍然未获取Class对象,则抛出ClassNotFoundException异常。

如果类需要resolve,在调用resolveClass进行链接。

 

    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 = findBootstrapClassOrNull(name);
		}
	    } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
	        // If still not found, then invoke findClass in order
	        // to find the class.
	        c = findClass(name);
	    }
	}
	if (resolve) {
	    resolveClass(c);
	}
	return c;
    }
findLoadedClass 此方法负责从当前ClassLoader实例对象缓存中寻找已加载类,调用为native方法。

 

 

    protected final Class<?> findLoadedClass(String name) {
	if (!checkName(name))
	    return null;
	return findLoadedClass0(name);
    }

    private native final Class findLoadedClass0(String name);

 

findClass 此方法直接抛出ClassNotFoundException异常,因此要通过覆盖loadClass或此方法来以自定义方式加载相应类。

 

    protected Class<?> findClass(String name) throws ClassNotFoundException {
	throw new ClassNotFoundException(name);
    }

 

findSystemClass 此方法是从sun.misc.Launcher$AppClassLoader中寻找类,如果未找到,则继续从BootstrapClassLoader中寻找,如果仍然未找到,返回null

 

    protected final Class<?> findSystemClass(String name)
	throws ClassNotFoundException
    {
	ClassLoader system = getSystemClassLoader();
	if (system == null) {
	    if (!checkName(name))
		throw new ClassNotFoundException(name);
            Class cls = findBootstrapClass(name);
            if (cls == null) {
                throw new ClassNotFoundException(name);
            } 
	    return cls;
	}
	return system.loadClass(name);
    }

 

defineClass 此方法负责将二进制字节流转换为Class对象,这个方法对于自定义类加载器而言非常重要。如果二进制字节码格式不符合jvm class文件格式规范,则抛出ClassFormatError异常;如果生成类名二进制字节码不同,则抛出NoClassDefFoundError;如果加载class是受保护、采用不同签名,或者类名是以java.开头,则抛出SecurityException异常。

 

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
					 ProtectionDomain protectionDomain)
	throws ClassFormatError
    {
         return defineClassCond(name, b, off, len, protectionDomain, true);
    }

    // Private method w/ an extra argument for skipping class verification
    private final Class<?> defineClassCond(String name,
                                           byte[] b, int off, int len,
                                           ProtectionDomain protectionDomain,
                                           boolean verify)
        throws ClassFormatError
    {
	protectionDomain = preDefineClass(name, protectionDomain);

	Class c = null;
        String source = defineClassSourceLocation(protectionDomain);

	try {
	    c = defineClass1(name, b, off, len, protectionDomain, source,
                             verify);
	} catch (ClassFormatError cfe) {
	    c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
                                       source, verify);
	}

	postDefineClass(c, protectionDomain);
	return c;
    }
resolveClass 此方法负责完成Class对象链接,如果链接过,则直接返回。

 

常见异常:

ClassNotFoundException 这是最常见异常,产生这个异常原因为在当前ClassLoader 中加载类时,未找到类文件,

NoClassDefFoundError 这个异常是因为 加载到类中引用到另外类不存在,例如要加载A,而A中盗用了B,B不存在或当前ClassLoader无法加载B,就会抛出这个异常。

LinkageError 该异常在自定义ClassLoader情况下更容易出现,主要原因是此类已经在ClassLoader加载过了,重复加载会造成该异常。

 

本文原文链接:http://blog.csdn.net/java2000_wl/article/details/8222876 转载请注明出处!

 

 

###4。

Java中Classloader
yjwang



发贴: 0
积分: 0
于 2003-03-16 20:01user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
Java中Classloader

作为一种编程语言, 我总觉得Java有那么一点奇异, 或者说混血: 它不象传统编译型语言(比如C/C++)那么纯粹, 它不仅仅是一种"语言". 比如Java中有Classloader概念, 而这通常是操作系统一部分. 理解这一概念对于J2EE尤其重要. 下面文章译自IBM一篇文档, 很清楚地解释了这个重要概念.

Classloader是如何工作?

Java虚拟机中每个Java类都是由某个classloader载入, classloader本身也是Java类, 所以这一点就特别有趣. 那么, 这些classloader又是如何载入呢? 这好像是一个悖论. 幸运是, 事实并非如此. Java包含一个自举classloader, 它是用本地代码写, 是JVM一部分. 这个自举classloader主要作用是载入Java核心类, 从而自举整个Java环境.

在一个企业Java应用中, 使用到许多类都不是Java核心类. 比如, 程序员也许会引用其应用中另外一个类, 或者Java扩展中一个类. Java扩展是扩展Java核心平台功能一些Java包. 为了隔离这两种不同Java类, Java采用两种不同classloader: applicationextension classloader. 它们都是用Java写. 这意味着这些类将被它们特定classloader载入, 如下例所示.
public class WhichClassLoader {
WhichClassLoader( ) {}
public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));
System.out.println("\nBootstrap classpath= \n"+ bootstrapClassPath + "\n");
System.out.println("Extension classpath= "+ extensionClassPath + "\n");
System.out.println("System classpath= "+ systemClassPath + "\n" );

//Create new object instances
java.lang.Object object = new java.lang.Object();
javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
WhichClassLoader whichClassLoader = new WhichClassLoader();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader, was loaded by: " + whichClassLoader1.getClass().getClassLoader() + "\n");
}
}

这个例子相当简单, 它查询显示classloader路径, 然后创建三个新对象示例, 类型各不相同: 一个Java核心类(java.lang.Object), 一个Java扩展类(javax.naming.InitialContext)一个用户类(WhichClassLoader). 最后它打印出载入这些类classloader. 运行这个例子结果是:
D:\Classpath_Project\src>java -classpath . WhichClassLoader
Bootstrap classpath= D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes
Extension classpath= D:\jdk1.2.2\jre\lib\ext
System classpath=
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f0272827
User file, WhichClassLoader, was loaded by: sun.misc.Launcher$AppClassLoader@f023282

这个结果中有几点值得一提. 比如, 你也许想知道为什么用户类是被sun.misc.Launcher$AppClassLoader@f023282所载入, 而不是更通用形式比如sun.misc.Launcher$AppClassLoader或者Application ClassLoader. 原因在于getClassLoader()方法返回类型是java.lang.ClassLoader. 当这个对象实例被送到一个输出流上时, 打印是实例名字, 这一名字在JVM每次启动时都会改变. 在我们这次特定运行中, application classloader实例名是sun.misc.Launcher$AppClassLoader@a1b1234. 对于extension classloader也是这样. 有趣是, 自举classloader实例名是空. 这是因为自举classloader不是用Java写, 所以没有java.lang.ClassLoader实例供返回.

另外一件引起你兴趣事情也许是我们如何通过系统类查询路径, 比如System.getProperties().getProperty("sun.boot.class.path"). 在这个例子中, 你也许会想我们可以通过动态地改变路径, 从而强制用一个特定classloader来载入一个特定类. 我们可以这样修改来验证一下这个想法:
import javax.naming.InitialContext;
public class WhichClassLoader1 {
//Constructor
WhichClassLoader1( ) {
//do nothing
}

public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));

//modifying the bootstrapclasspath to include the jar file which contains the InitialContext Class
// This should force the class to be loaded by the bootstrap classloader???????????
String fileSeparator = System.getProperty("file.separator");
String InitialContextJar = "D:"+fileSeparator+"jdk1.2.2"+fileSeparator + "jre"+fileSeparator+"lib"+fileSeparator+"ext"+fileSeparator+"iioprt.jar"
bootstrapClassPath.append(System.getProperty("path.separator")).append(InitialContextJar);
System.setProperty("sun.boot.class.path",bootstrapClassPath.toString());
System.out.println("\nBootstrap classpath=\n"+ bootstrapClassPath + "\n");
System.out.println("Extension classpath="+ extensionClassPath + "\n");
System.out.println("System classpath="+ systemClassPath + "\n" );

//Create new object instances
Object object = new java.lang.Object()
InitialContext initialContext = new InitialContext();
WhichClassLoader1 whichClassLoader1 = new WhichClassLoader1();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, Javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader1, was loaded by: " + whichClassLoader1.getClass().getClassLoader() + "\n");
}
}

运行结果很有趣:
Bootstrap classpath=D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\iioprt.jar
Extension classpath=D:\jdk1.2.2\jre\lib\ext
System classpath=.
Java Core file,java.lang.Object, was loaded by: null
Extension file, Javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b290a
User file, WhichClassLoader1, was loaded by: sun.misc.Launcher$AppClassLoader@f01f290a

我们期待不同. 事实是改变这些系统级属性完全没有效果. 这说明了这些classloader很重要一个特性: 一旦JVM启动后, 它们路径是不可更改, 或者说是静态.

让我们回到这个例子原来. 到目前为止, 看上去Javaclassloader正如我们被告知那样工作: 自举classloader载入Java核心类, extension classloader载入Java扩展包, 而application classloader载入用户类. 然而, classloader在本质上又是分层, 遵循"委托给父类"模式. 这意味着除了自举classloader, 每一个classloader都有一个父classloader, 当一个classloader试图载入一个类时, 它首先将这一责任委托给它父classloader. 这个模式是这样工作: 一个classloader将尝试载入一个类, 当且仅当这个类在这个classloader所属层次结构中还未被载入, 并且这个classloader父classloader找不到这个类.

自顶向下地遍历classloader层次, 我们看到首先是自举classloader, 然后是extension classloaderapplication classloader. 如果你将classloader层次看作稀疏树结构, 那么自举classloader是根节点, application classloader是叶节点.

这种层次结构可以写程序演示如下:
public class ShowHierarchy{
public static void main (String args[] ) throws Exception {
System.out.println("The System ClassLoader is: " + ClassLoader.getSystemClassLoader().getClass());
System.out.println("The System ClassLoader's Parent is: " + ClassLoader.getSystemClassLoader().getParent().getClass());
System.out.println("The System ClassLoader's Parent's Parent is: " +ClassLoader.getSystemClassLoader().getParent().getParent());
}
}
结果是:
D:\WebSphere\Documents\Classpaths\src>java .classpath . ShowHierarchy
The System ClassLoader is: class sun.misc.Launcher$AppClassLoader
The System ClassLoader's Parent is: class sun.misc.Launcher$ExtClassLoader

为了演示"委托给父类"关系, 让我们在每个classloader路径上放一份包含InitialContext.classJAR文件, 然后执行第一个例子. 记住一旦JVM运行以后, 它classloader路径是不可变, 所以我们需要通过Java开关-classpath-bootclasspath在运行之前改变它们.
D:\Classpath_Project\src\java -classpath D:\jdk1.2.2\jre\lib\ext\jndi.jar;. -XbootclasspathD:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\jndi.jar WhichClassLoader
Bootstrap classpath= D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes;D:\jdk1.2.2\jre\lib\ext\jndi.jar
Extension classpath= D:\jdk1.2.2\jre\lib\ext
System classpath= D:\jdk1.2.2\jre\lib\ext\jndi.jar;.
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: null
User file, WhichClassLoader, was loaded by: sun.misc.Launcher$AppClassLoader@f01a2987

正如你所见, InitialContext类如我们预见那样是由自举classloader载入, 你能理解这是为什么吗? 让我们详细描述一下发生事吧:
1. WhichClassLoader类创建一个InitialContext类新实例: javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
2. Application classloader查看InitialContext类是否已经在它所属classloader层次结构中被载入, 也就是说被自举classloader, 被extension classloader或者被它自己所载入.
3. 结果是否定.
4. 遵循"委托给父类"模式, application classloader将载入任务委托给extension classloader.
5. 再一次遵循"委托给父类"模式, extension classloader将载入任务委托给自举classloader.
6. 自举classloader没有父classloader, 尝试载入类.
7. 自举classloader成功地载入了InitialContext类.
8. InitialContext新实例被创建并返回给WhichClassloader.

这一场景需要进一步解释. 看上去似乎很明显是, application classloader是第一个收到创建InitialContext新实例classloader, 因为它位于classloader层次结构最底层. 但并不总是这样. 随着Java 2出现, 类将通过调用者classloader被载入, 这可能是, 但也可能不是位于层次结构最底层classloader. 在我们例子中, 调用者是WhichClassLoader, 我们知道它是由application classloader载入.

Java 2中classloader遵循"委托给父类, 并组织成层次结构"模式, 这允许你做一些有趣事情. 但当类不是由classloader层次结构叶节点载入时, 也可能引起问题. 比如, 让我们修改一下WhichClassLoader, 重命名为WhichClassLoader2, 并创建两个新类WhichClassLoader3WhichClassLoader4.

文件WhichClassLoader2.java:
public class WhichClassLoader2 {
//Constructor
WhichClassLoader2() {
//do nothing
}
public static void main (String args[] ) throws Exception {
//Retrieve the classpaths
StringBuffer bootstrapClassPath=new StringBuffer(System.getProperties().getProperty("sun.boot.class.path"));
StringBuffer extensionClassPath=new StringBuffer(System.getProperties().getProperty("java.ext.dirs"));
StringBuffer systemClassPath=new StringBuffer(System.getProperties().getProperty("java.class.path"));

System.out.println("\nBootstrap classpath="+ bootstrapClassPath + "\n");
System.out.println("Extension classpath="+ extensionClassPath + "\n");
System.out.println("System classpath="+ systemClassPath + "\n" );
//Create new object instances
java.lang.Object object = new java.lang.Object();
javax.naming.InitialContext initialContext = new javax.naming.InitialContext();
WhichClassLoader2 whichClassLoader2 = new WhichClassLoader2();
WhichClassLoader3 whichClassLoader3 = new WhichClassLoader3();
System.out.println("\nJava Core file,java.lang.Object, was loaded by: " + object.getClass().getClassLoader());
System.out.println("\nExtension file, javax.naming.InitialContext, was loaded by: " + initialContext.getClass().getClassLoader());
System.out.println("\nUser file, WhichClassLoader2, was loaded by: " + whichClassLoader2.getClass().getClassLoader() + "\n");
System.out.println("\nUser file, WhichClassLoader3, was loaded by: " + whichClassLoader3.getClass().getClassLoader() + "\n");
whichClassLoader3.getTheClass();
}
}

文件WhichClassloader3.java:
public class WhichClassLoader3 {
public WhichClassLoader3 () {
}
public void getTheClass() {
WhichClassLoader4 wcl4 = new WhichClassLoader4 ();
System.out.println("WhichClassLoader4 was loaded by " + wcl4.getClass().getClassLoader());
}
}

文件WhichClassloader4.java:
public class WhichClassLoader4 {
WhichClassLoader4 () {
}
}

我们将WhichClassLoader2WhichClassLoader4放在当前目录下, 这样它们仅仅存在于application classloader路径之中. 接下来我们将WhichClassLoader3放在extension classloader路径中.
D:\Classpath_Project\src>ls
WhichClassLoader2.class
WhichClassLoader2.java
WhichClassLoader1.java
WhichClassLoader4.class
WhichClassLoader4.java
D:\Classpath_Project\src>ls D:\jdk1.2.2\jre\lib\ext\
WhichClassLoader3.jar
cosnaming.jar
iiimp.jar
iioprt.jar
jndi.jar
providerutil.jar
rmiorb.jar
rmiregistry.jar

我们来看一下运行WhichClassLoader2会发生什么.
D:\Classpath_Project\src>java -classpath . WhichClassLoader2
Bootstrap classpath=D:\jdk1.2.2\jre\lib\rt.jar;D:\jdk1.2.2\jre\lib\i18n.jar;D:\jdk1.2.2\jre\classes
Extension classpath=D:\jdk1.2.2\jre\lib\ext
System classpath=.
Java Core file,java.lang.Object, was loaded by: null
Extension file, javax.naming.InitialContext, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b3492
User file, WhichClassLoader2, was loaded by: sun.misc.Launcher$AppClassLoader@f01f3492
User file, WhichClassLoader3, was loaded by: sun.misc.Launcher$ExtClassLoader@f01b3492
java.lang.NoClassDefFoundError: WhichClassLoader4
at WhichClassLoader3.getTheClass(WhichClassLoader3.java:8)
at WhichClassLoaderTest.main(WhichClassLoaderTest.java:38)
Exception in thread "main"

你也许会问既然WhichClassLoader4明明在application classloader路径中, 载入WhichClassLoader4为什么会失败. 这种失败不是因为classloader找不到类, 而是因为WhichClassLoader3classloader找不到类. 为了更好地理解发生事情, 让我们从载入WhichClassLoader3classloader(也就是extension classloader)角度来看一下.

首先, 要求创建一个WhichClassLoader4实例, 于是遵循Java 2"委托给父类"模式, extension classloader先查看这个类是否已经被它所属classloader层次结构所载入, 这种尝试将失败. 于是它请求它父classloader, 就是自举classloader, 来载入这个类. 这一请求将返回一个NoClassDefFound异常. Extension classloader将捕获这个异常并求助于它最后选择: 它试图自己载入这个类. 搜寻了自己搜索路径之后, extension classloader找不到WhichClassLoader4类定义, 所以它重新抛出NoClassDefFoundError异常. 此时不再有classloader来捕获这个异常, 于是在屏幕上打印异常, 程序退出.

这就引起一个问题, 既然有可能发生这样问题, 为什么要自寻烦恼地采用多个classloader呢? 为什么不回到Java 2之前框架, 在那样框架之下, 只有一个系统classloader来载入所有东西? 与引发问题相比, classloader层次结构"委托给父类"模式带来好处是否值得呢? 简而言之, 是值得:
1. 保护. Java预定义了自举classloaderextension classloader, 缺省情况下, 只把application classloader定义留给了用户. 因为"委托给父类"模式, 用户定义任何类都不可能覆盖Java扩展类核心类.
2. 用户友好. 在同样方式下, 因为自举classloaderextension classloader是预定义, 你不再需要把classes.zip文件放在-classpath属性中.
3. 隔离. 也许这是新classloader定义做提供最重要好处. 到目前为止, 这看上去不是那么有用, 但我们还没有谈到Java允许你定义自己classloader. 使用继承, 可以形成自己classloader层次树.

理解这个模型所提供益处最佳方式是操作系统名字空间作一个类比. 让我们假设我们正在开发一个叫MyApp应用, 这个应用有三个版本: 一个是实际用, 一个处于Beta测试中, 一个还在开发中. 我们想要在任何时候运行这些应用程序版本中任何一个, 于是我们将它们放在分开目录结构中:
/usr/MyApp
/usr/MyApp/v1
/usr/MyApp/v2
/usr/MyApp/v3

所有版本公用文件放在MyApp目录下, 版本特定文件分别放在v1, v2v3目录下. 使用这种方法, 我们能很有效地使用硬盘空间, 同时又能管理版本差异. 同样想法对classloader也适用. 核心类是最通用, 就放在层次结构根节点上, 而那些特定"版本"类放在叶节点上.

译者注: 比较了WebSphere, WebLogicJBoss之后, 发现它们classloader设计都不一样. 移植代码时, 这可能引发一些问题.
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics