虚拟机类加载机制--类加载器

准备阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到了Java虚拟机外部去实现,以便让应用程序自己决定如何如获取所需要的类。实现这个动作的代码模块称为“类加载器”

1.类与类加载器

每一个类加载器都有一个独立的类名称空间,由类加载器和类一起合作才能确定一个类在虚拟机中的唯一性。也就是说:比较两个类是否“相等”,即使他们来自同一个Class文件,在同一个虚拟机上被加载,如果加载它们的类加载器不同,那么这两个类就不相等。

这里的“相等”,包括类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法,以及使用instanceof关键字作对象所属关系判定等情况。

2.双亲委派模型

虚拟机角度来说,有两种类加载器:

一种是启动类加载器,使用C++语言实现。

另一种就是所有其他的类加载器,这些类加载器都由java语言实现,独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader。

按照开发人员的角度来看,绝大部分java程序都会使用到以下3种系统提供的类加载器:

启动类加载器:负责加载存放在%JAVA_HOME%\lib目录中的,或者通被-Xbootclasspath参数所指定的路径中的,并且被java虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库,即使放在指定路径中也不会被加载)类库到虚拟机的内存中,启动类加载器无法被java程序直接引用

扩展类加载器:由sun.misc.Launcher$ExtClassLoader实现,负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

应用程序类加载器:由sun.misc.Launcher$AppClassLoader实现,负责加载用户类路径classpath上所指定的类库,是类加载器ClassLoader中的getSystemClassLoader()方法的返回值,开发者可以直接使用应用程序类加载器,如果程序中没有自定义过类加载器,该加载器就是程序中默认的类加载器。

类加载器之间的这种层次关系,称为类加载器的双亲委派模型。这里类加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合关系来复用父加载器的代码。

 

//验证类加载器与类加载器间的父子关系  
    public static void main(String[] args) throws Exception{  
        //获取系统/应用类加载器  
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();  
        System.out.println("系统/应用类加载器:" + appClassLoader);  
        //获取系统/应用类加载器的父类加载器,得到扩展类加载器  
        ClassLoader extcClassLoader = appClassLoader.getParent();  
        System.out.println("扩展类加载器" + extcClassLoader);  
        System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));  
        //获取扩展类加载器的父加载器,但因根类加载器并不是用Java实现的所以不能获取  
        System.out.println("扩展类的父类加载器:" + extcClassLoader.getParent());  
    }  

类加载器的双亲委派加载机制(重点):当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。

使用双亲委派模型的好处:java类随着它的类加载器一起具备了一种带有优先级的层次关系,例如类java.lang.Object,它存放在rt.java之中,无论哪一个类要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在程序的各种类加载器环境中都是一个类。

双亲委派模型的实现:

主要体现在ClassLoader的loadClass()方法中,思路很简单:先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载器加载失败,抛出ClassNotFoundException异常后,调用自己的findClass()方法进行加载。

1. 执行findLoadedClass(String)去检测这个class是不是已经加载过了。 
2. 执行父加载器的loadClass方法。如果父加载器为null,则jvm内置的加载器去替代,也就是Bootstrap ClassLoader。这也解释了ExtClassLoader的parent为null,但仍然说Bootstrap ClassLoader是它的父加载器。 
3. 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。

如果class在上面的步骤中找到了,参数resolve又是true的话,那么loadClass()又会调用resolveClass(Class)这个方法来生成最终的Class对象。 我们可以从源代码看出这个步骤。

public Class<?> loadClass(String name) throws ClassNotFoundException {  
        return loadClass(name, false);  
    }  
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;  
}  

下面看一个简单的双亲委派模型代码实例验证

public class ClassLoaderTest {  
    public static void main(String[] args){  
        //输出ClassLoaderText的类加载器名称  
        System.out.println("ClassLoaderText类的加载器的名称:"+ClassLoaderTest.class.getClassLoader().getClass().getName());  
        System.out.println("System类的加载器的名称:"+System.class.getClassLoader());  
        System.out.println("List类的加载器的名称:"+List.class.getClassLoader());  
  
        ClassLoader cl = ClassLoaderTest.class.getClassLoader();  
        while(cl != null){  
                System.out.print(cl.getClass().getName()+"->");  
                cl = cl.getParent();  
        }  
        System.out.println(cl);  
}  

 3.破坏双亲委派模型

双亲委派模型并不是一个强制性的约束模型,但是java中大部分的类加载器都遵循这个模型,但也有意外,目前为止,双亲委派模型中主要出现过三次较大规模的“被破坏”的情况

 

posted @ 2017-07-30 16:05  竹马今安在  阅读(255)  评论(0编辑  收藏  举报