类加载器(双亲委派模型)

1.类与类加载器

    对于任何一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。(比较两个类是否’相等’,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源一个同一个class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等)

    这里所指的‘相等’,包括类的Class对象的equals()方法,isAssignableFrom()方法,isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判断等情况。

import java.io.IOException;
import java.io.InputStream;

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if(is==null){
                        return  super.loadClass(name);
                    }
                    byte[] b=new byte[is.available()];
                    is.read(b);
                    return defineClass(name,b,0,b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object obj =myLoader.loadClass("ClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof ClassLoaderTest);
    }
}

    《类加载器(双亲委派模型)》

    这个类加载器加载名为ClassLoaderTest的类并实例化了这个类的对象。从两行输出结果可以看到,第一行输出确定是类ClassLoaderTest实例化的对象,但是第二句可以发现这个对象与类ClassLoaderTest做的属性检查却返回了false,这是因为虚拟机中存在了两个ClassLoaderTest,一个是系统应用程序类加载器加载的,一个是我们自定义的类加载器加载的,虽然是同一个Class文件但是却是独立的两个类,做的对象所属类型检查时结果就是false.

2.双亲委派模型

    从java虚拟机角度来讲只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器用c++实现(只限于HotSpot),是虚拟机自身的一部分;另一种是所有其他的类加载器,由java实现,独立于虚拟机外部,并且都继承自抽象类java.lang.ClassLoader。

    从开发人员的角度来看,类加载器可以划分的更细致点,绝大部分java程序都会用到以下三种系统提供的类加载器。

    1.启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被java程序直接引用,在编写自定义类加载器是,如果需要把加载请求委派给引导类加载器,那直接用null代替即可。

《类加载器(双亲委派模型)》

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

    3.应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

《类加载器(双亲委派模型)》

    双亲委派模型要求除了顶层启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器直接的父子关系不会以继承实现,是使用组合关系来复用父加载器的代码。

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

    使用双亲委派模型来组织类加载器直接的关系,显而易见的好处就是java类随它的类加载器一起具备了一种带有优先级的层次关系。如java.lang.Object,它存放在rt.jar.无论哪个类加载器加载这个类,最终都是委派给最顶端的启动类加载器进行加载。因此Object类在程序的各种类加载器环境中都是一个类。否则如果没有使用双亲委派模型,由各个类加载器自行加载,再程序的ClassPath中存放用户自己编写的java.lang.Object类。那系统中将会出现多个不同的Object类,应用程序将会一片混乱。

    实现双亲委派的代码集中在java.lang.ClassLoader的loadClass()方法中:先检查是否已经被加载过,如果没有加载则调用父加载器的loadClass(),若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,抛出ClassNotFoundException异常后,再调用findClass方法进行加载。

《类加载器(双亲委派模型)》

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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 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.
                    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;
        }
    }

jdk1.8的loadclass方法。

3.“破坏”双亲委派模型

    历史上一共有三次双亲委派模型被”破坏”的情况。

第一次是因为向前兼容JDK1.2以前的版本所添加的protected方法findClass(),在此之前用户去继承ClassLoader的唯一目的就是为了重写loadClass()方法。

第二次是为了能让基础类调用用户代码。如典型的例子JNDI,为了解决这个问题引入了线程上下文加载器,通过线程上下文使父类加载器请求子类加载器完成类加载,采用这种模式的例有:JNDI,JDBC,JCE,JAXB,JBI等(所有涉及SPI的加载动作几乎都采用这种模式)。

第三次是由于用户对程序动态性的追求导致的(代码热替换,模块热部署)。其中OSGI中对类加载器的使用十分值得学习。

(ps:因为篇幅的关系这里对破坏双亲委派模型这块不做过多讲述,有需要的同学请自行查询资料。如果发现有什么纰漏或是说错的地方请留言。内容采自深入理解java虚拟机 jvm高级特性与最佳实践

    原文作者:蛇皮皮蛋
    原文地址: https://blog.csdn.net/qq_37598011/article/details/80500962
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞