简述java类加载机制ClassLoader及双亲委派模型

类加载器ClassLoader:

用于将Java类加载到Java虚拟机中,其常用的有以下三种,当然除此之外还有用户自定义类加载器

  1. 根加载器(Bootstrap)
  2. 扩展加载器
  3. 应用程序类加载器

根加载器(BootstrapClassLoader):

C++编写。JVM的核心加载器,随Java面世的第一版加载器。我们之所以安装好java,配置好环境变量,运行起java,就可以直接使用Object obj = new Object(),List list = new ArrayList()之类代码,是因为根加载器默认将${JAVA_HOME}/jre/lib/rt.jar加载到虚拟机中。

扩展加载器(ExtensionClassLoader):

Java编写。由于Java诞生后不断的扩展优化,单独使用扩展类加载器加载${JAVA_HOME}/jre/lib/ext目录下的jar,库名通常以javax开头,例如swing等;扩展类库主要是为了兼容旧版本,但某些东西又有了新的解决方案,于是提供扩展类库。

应用程序类加载器(AppClassLoader):

面向程序员的加载器,会加载环境变量中CLASSPATH下的jar,自己生成的类或者第三方的类均有该加载器加载。

程序测试:

List list = new ArrayList();// java包
SwingNode swingNode = new SwingNode();// javax包
Student student = new Student();// 自定义java类

System.out.println(list.getClass().getClassLoader());
System.out.println(swingNode.getClass().getClassLoader());
System.out.println(student.getClass().getClassLoader());

运行结果: 

 可以看到三种java文件对应的类加载器,根加载器为null,扩展加载器及应用程序加载器都有一个sun.misc.Launcher前缀,实际上看下图路径,该Launcher类就是JVM的入口。

 通过代码体现父类继承关系:

List list = new ArrayList();
SwingNode swingNode = new SwingNode();
Student student = new Student();

System.out.println(swingNode.getClass().getClassLoader().getParent());
System.out.println(student.getClass().getClassLoader().getParent());
System.out.println(student.getClass().getClassLoader().getParent().getParent());

 运行结果:

双亲委派模型

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的父类加载器,如果父的类加载器没有加载,子类加载器才会尝试去加载。通俗的说就是:不论哪个class文件需要加载,首先给根加载器,若根加载器没找到,则交给扩展类加载器,如果还没找到,才会给应用程序类加载器,再找不到就会报ClassNotFound异常。

 

ClassLoader源码分析

ClassLoader为一个抽象类,在虚拟机启动后进行类加载会首先调用loadClassInternal方法,而该方法指向loadClass方法加载class文件,而双亲委派模型就是在该方法中实现。该方法首先判断该类是否已经被加载,若没被加载,则调用parent.loadClass方法加载(即父加载器),parent.loadClass方法中依然会判断parent是否为null,若依然有父类加载器,则继续递归向上调用,直至parent==null。当其父加载器抛出ClassNotFoundException时,说明没找到,则由子加载器继续加载,符合双亲委派机制。

public abstract class ClassLoader {

    private final ClassLoader parent;

    // 该方法在虚拟机启动进行类加载时会立刻调用
    private Class<?> loadClassInternal(String name)
        throws ClassNotFoundException
    {
        if (parallelLockMap == null) {
            synchronized (this) {
                 return loadClass(name);
            }
        } else {
            return loadClass(name);
        }
    }

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查请求的类是否已经被加载过了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父类加载器抛出ClassNotFoundException
                    // 说明父类加载器无法完成加载请求
                }

                if (c == null) {
                    // 在父类加载器无法加载时
                    // 再调用本身的findClass方法进行类加载
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
}

好处

保证源代码不受污染。假设你自己定义了一个java.lang.String类,去运行,若没有此机制,会污染原本jdk中带的String类,导致代码出现不可预知的问题,程序将变得一片混乱。

破坏双亲委派模型

破坏历史

在Java诞生以来,历史上有三次大规模破坏:

第一次:双亲委派模型为JDK1.2推出,但在此之前就已经存在了ClassLoader,因此需要兼容。上述ClassLoader源码在loadClass方法中实现的双亲委派,因此如果想破坏最直接的办法就是继承ClassLoader并重写loadClass方法,该方法无疑是破坏性的,但因为JDK1.2以前都是这么做的,该接口无奈保留支持重写,但新的代码绝对不推荐如此使用。

ClassLoader在JDK1.2之后新增了一个findClass方法,目的就是当父类加载器加载失败时,将子类加载器的逻辑写在findClass中完成自定义设计。

第二次:

什么时候需要打破

未完待续。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章