JVM学习系列

【JVM】浅谈双亲委派和破坏双亲委派

目录一、前言二、双亲委派2.1、为什么需要双亲委派2.2、双亲委派的实现三、破坏双亲委派3.1、为什么需要破坏双亲委派?3.2、破坏双亲委派的实现四、总结一、前言笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书,阅读完后自以为对jvm有了一定的了解,然而当真正碰到问题的时候,才发现自己读的有多粗糙,也体会到只有实践才能加深理解,正应对了那句话——“Talkischeap,showmethec

目录

一、前言

二、双亲委派

2.1、为什么需要双亲委派

2.2、双亲委派的实现

三、破坏双亲委派

3.1、为什么需要破坏双亲委派?

3.2、破坏双亲委派的实现

四、总结

一、前言

笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书,阅读完后自以为对jvm有了一定的了解,然而当真正碰到问题的时候,才发现自己读的有多粗糙,也体会到只有实践才能加深理解,正应对了那句话——“Talk is cheap, show me the code”。前段时间,笔者同事提出了一个关于类加载器破坏双亲委派的问题,以我们常见到的数据库驱动Driver为例,为什么要实现破坏双亲委派,下面一起来重温一下。


二、双亲委派

想要知道为什么要破坏双亲委派,就要先从什么是双亲委派说起,在此之前,我们先要了解一些概念:

  • 对于任意一个类,都需要由加载它的类加载器和这个类本身来一同确立其在Java虚拟机中的唯一性。

什么意思呢?我们知道,判断一个类是否相同,通常用equals()方法,isInstance()方法和isAssignableFrom()方法。来判断,对于同一个类,如果没有采用相同的类加载器来加载,在调用的时候,会产生意想不到的结果:

public class DifferentClassLoaderTest {    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = new ClassLoader() {            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                InputStream stream = getClass().getResourceAsStream(fileName);                if (stream == null) {                    return super.loadClass(name);
                }                try {                    byte[] b = new byte[stream.available()];                    // 将流写入字节数组b中
                    stream.read(b);                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                }                return super.loadClass(name);
            }
        };
        Object obj = classLoader.loadClass("jvm.DifferentClassLoaderTest").newInstance();
        System.out.println(obj.getClass());
        System.out.println(obj instanceof DifferentClassLoaderTest);

    }
}

输出结果:

class jvm.DifferentClassLoaderTestfalse

如果在通过classLoader实例化的使用,直接转化成DifferentClassLoaderTest对象:

DifferentClassLoaderTest obj = (DifferentClassLoaderTest) classLoader.loadClass("jvm.DifferentClassLoaderTest").newInstance();

就会直接报 java.lang.ClassCastException:,因为两者不属于同一类加载器加载,所以不能转化!


2.1、为什么需要双亲委派

基于上述的问题:如果不是同一个类加载器加载,即时是相同的class文件,也会出现判断不想同的情况,从而引发一些意想不到的情况,为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采用了双亲委派的方式来加载类。

双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

这里有几个流程要注意一下:

  1. 子类先委托父类加载

  2. 父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类

  3. 子类在收到父类无法加载的时候,才会自己去加载

jvm提供了三种系统加载器:

  1. 启动类加载器(Bootstrap ClassLoader):C++实现,在java里无法获取,负责加载<JAVA_HOME>/lib下的类。

  2. 扩展类加载器(Extension ClassLoader): Java实现,可以在java里获取,负责加载<JAVA_HOME>/lib/ext下的类。

  3. 系统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。

附上三者的关系:

310ed1abf70f50b9f997d0e1b72ff9a4.png


2.2、双亲委派的实现

双亲委派的实现其实并不复杂,其实就是一个递归,我们一起来看一下ClassLoader里的代码:

protected Class<?> loadClass(String name, boolean resolve)    throws ClassNotFoundException
    {    	// 同步上锁
        synchronized (getClassLoadingLock(name)) {            // 先查看这个类是不是已经加载过
            Class<?> c = findLoadedClass(name);            if (

原创不易,完成人机校验,阅读全文

相关推荐