Java类加载器(Class Loader)之详解

本文主要总结一下我对Java类加载器(Class Loader)认识,如有不准确之处还望大侠不吝赐教!

关键字:Java,类加载器(Class Loader)

主要从如下几个部分进行介绍:

类加载的过程
类加载器的层级
类加载器的操作原则
为什么要个性化类加载器
其它相关问题
类加载的过程
类加载器的主要工作就是把类文件加载到JVM中。如下图所示,其过程分为三步:

加载:定位要加载的类文件,并将其字节流装载到JVM中;
链接:给要加载的类分配最基本的内存结构保存其信息,比如属性,方法以及引用的类。在该阶段,该类还处于不可用状态;
验证:对加载的字节流进行验证,比如格式上的,安全方面的;
内存分配:为该类准备内存空间来表示其属性,方法以及引用的类;
解析:加载该类所引用的其它类,比如父类,实现的接口等。
初始化:对类变量进行赋值。





类加载器的层级
下图虚线以上是JDK提供的几个重要的类加载器,详细说明如下:

Bootstrap Class Loader: 当启动包含主函数的类时,加载JAVA_HOME/lib目录下或-Xbootclasspath指定目录的jar包;
Extention Class Loader:加载JAVA_HOME/lib/ext目录下的或-Djava.ext.dirs指定目录下的jar包。
System Class Loader:加载classpath或者-Djava.class.path指定目录下的类或jar包。



需要了解的是:

除了Bootstrap Class Loader外,其它的类加载器都是java.lang.ClassLoader类的子类;
Bootstrap Class Loader不是用Java实现,如果你没有使用个性化类加载器,那么java.lang.String.class.getClassLoader()就为null,Extension Class Loader的父加载器也为null;
获得类加载器的几种方式:
获得Bootstrap Class Loader:试图获得Bootstrap Class Loader,得到的必然是null。可以用如下方式验证下:使用rt.jar包内的类对象的getClassLoader方法,比如java.lang.String.class.getClassLoader()可以得到或者获得Extention Class Loader,再调用getParent方法获得;
获得Extention Class Loader:使用JAVA_HOME/lib/ext目录下jar包内的类对象的getClassLoader方法或者先获得System Class Loader,再通过它的getParent方法获得;
获得System Class Loader:调用包含主函数的类对象的getClassLoader方法或者在主函数内调用Thread.currentThread().getContextClassLoader()或者调用ClassLoader.getSystemClassLoader();
获得User-Defined Class Loader:调用类对象的getClassLoader方法或者调用Thread.currentThread().getContextClassLoader();
类加载器的操作原则
代理原则
可见性原则
唯一性原则
代理原则
代理原则指的是一个类加载器在加载一个类时会请求它的父加载器代理加载,父加载器也会请求它的父加载器代理加载,如下图所示。




为什么要使用代理模式呢?首先这样可以减少重复的加载一个类。(还有其它原因吗?)

容易误解的地方:

一般会以为类加载器的代理顺序是Parent First的,也就是:

加载一个类时,类加载器首先检查自己是否已经加载了该类,如果已加载,则返回;否则请父加载器代理;
父加载器循环重复1的操作一直到Bootstrap Class Loader;
如果Bootstrap Class Loader也没有加载该类,将尝试进行加载,加载成功则返回;如果失败,抛出ClassNotFoundException,则由子加载器进行加载;
子类加载器捕捉异常后尝试加载,如果成功则返回,如果失败则抛出ClassNotFoundException,直到发起加载的子类加载器。
这种理解对Bootstrap Class Loader,Extention Class Loader,System Class Loader这些加载器是正确的,但一些个性化的加载器则不然,比如,IBM Web Sphere Portal Server实现的一些类加载器就是Parent Last的,是子加载器首先尝试加载,如果加载失败才会请父加载器,这样做的原因是:假如你期望某个版本log4j被所有应用使用,就把它放在WAS_HOME的库里,WAS启动时会加载它。如果某个应用想使用另外一个版本的log4j,如果使用Parent First,这是无法实现的,因为父加载器里已经加载了log4j内的类。但如果使用Parent Last,负责加载应用的类加载器会优先加载另外一个版本的log4j。(更多信息)


可见性原则
每个类对类加载器的可见性是不一样的,如下图所示。

扩展知识,OSGi就是利用这个特点,每一个bundle由一个单独的类加载器加载,因此每个类加载器都可以加载某个类的一个版本,因此整个系统就可以使用一个类的多个版本。

唯一性原则
每一个类在一个加载器里最多加载一次。

扩展知识1:准确地讲,Singleton模式所指的单例指的是在一组类加载器中某个类的对象只有一份。

扩展知识2:一个类可以被多个类加载器加载,每个类对象在各自的namespace内,对类对象进行比较或者对实例进行类型转换时,会同时比较各自的名字空间,比如:

Klass类被ClassLoaderA加载,假设类对象为KlassA;同时被ClassLoaderB加载,假设类对象为KlassB,那么KlassA不等于KlassB。同时ClassA的实例被cast成KlassB时会抛出ClassCastException异常。
为什么要个性化类加载器
个性化类加载器给Java语言增加了很多灵活性,主要的用途有:

可以从多个地方加载类,比如网络上,数据库中,甚至即时的编译源文件获得类文件;
个性化后类加载器可以在运行时原则性的加载某个版本的类文件;
个性化后类加载器可以动态卸载一些类;
个性化后类加载器可以对类进行解密解压缩后再载入类。
其它相关问题
类的隐式和显式加载
隐式加载:当一个类被引用,被继承或者被实例化时会被隐式加载,如果加载失败,是抛出NoClassDefFoundError。

显式加载:使用如下方法,如果加载失败会抛出ClassNotFoundException


cl.loadClass(),cl是类加载器的一个实例;
Class.forName(),使用当前类的类加载器进行加载。
类的静态块的执行
假如有如下类:

[java] view plaincopy
package cn.fengd; 
 
public class Dummy { 
    static { 
        System.out.println("Hi"); 
    } 


另建一个测试类:
[java] view plaincopy
package cn.fengd; 
 
public class ClassLoaderTest { 
 
    public static void main(String[] args) throws InstantiationException, Exception { 
 
        try { 
            /*
             * Different ways of loading.
             */ 
            Class c = ClassLoaderTest.class.getClassLoader().loadClass("cn.fengd.Dummy"); 
        } catch (Exception e) { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
        } 
    } 
 


运行后效果如何呢?
不会输出Hi。由此可见使用loadClass后Class类对象并没有初始化;

如果在Load语句后加上c.newInstance(); 就会有Hi输出,对该类进行实例化时才初始化类对象。

如果换一种加载语句Class c = Class.forName("cn.fengd.Dummy", false, ClassLoader.getSystemClassLoader());

不会输出Hi。因为参数false表示不需要初始化该类对象;

如果在Load语句后加上c.newInstance(); 就会有Hi输出,对该类进行实例化时才初始化类对象。
如果换成Class.forName("cn.fengd.Dummy");或者new Dummy()呢?

都会输出Hi。
转自http://blog.csdn.net/radic_feng/article/details/6897898

猜你喜欢

转载自haidaoqi3630.iteye.com/blog/2203239
今日推荐