zoukankan      html  css  js  c++  java
  • 虚拟机类加载机制--类加载器

    准备阶段的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到了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%libext目录中的,或者被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中大部分的类加载器都遵循这个模型,但也有意外,目前为止,双亲委派模型中主要出现过三次较大规模的“被破坏”的情况

     

  • 相关阅读:
    【HDOJ】4370 0 or 1
    【HDOJ】4122 Alice's mooncake shop
    【HDOJ】4393 Throw nails
    【HDOJ】2385 Stock
    WinCE 输入法编程
    WinCE 自由拼音输入法的测试
    SQL Server CE开发环境建立过程
    【SQL Server CE2.0】创建加密的数据库(源代码)
    【SQL Server CE2.0】打开加密的数据库(源代码)
    EVC在双核PC上调试速度慢的原因
  • 原文地址:https://www.cnblogs.com/wxw7blog/p/7259426.html
Copyright © 2011-2022 走看看