浅析:JVM双亲委派模型

352 阅读4分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

书接上文的类加载模式,本节我们看下类加载双亲委派

什么是双亲委派

对于.class字节码文件,我们的JVM虚拟机需要通过类加载器来加载他们,但是类加载器众多,我们无法确定该由哪个类来进行加载!所以java首先加载的时候不会直接加载该类,而是会把这个任务委托给他的上级类加载器加载,重复这个操作,只有上级无法加载该.class文件时,自己才会去加载这个.class。

类加载器类型

**Bootstrap ClassLoader(启动类加载器),**C++实现,加载Java基础类%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等

**Extension ClassLoader(标准扩展类加载器)**加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。

Application ClassLoader (应用类加载器) 加载当前应用的classpath下的所有jar和类

**用户自定义类加载器 ** 加载指定路径下的class文件

以上的只是针对1.8及之前的jdk版本,在1.9之后引入的模块化的概念,以前的基础jar包会被拆分成很多的模块,编译的时候只编译实际用到的模块,各个类加载器各司其职,只加载自己负责的模块。

为什么要这样一层一层去请求呢?因为通过这种方式可以避免类的重复加载,保证了代码的安全性,不至于别人恶意破坏基础库或者某些代码的冲突;

实现该功能的主要方法是loadClass()

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;
        }
    }

具体意思我们直接看api的说明

加载具有指定二进制名称的类。 此方法的默认实现按以下顺序搜索类:
调用findLoadedClass(String)来检查类是否已经加载。
在父类加载器上调用loadClass方法。 如果 parent 为null ,则使用虚拟机内置的类加载器。
调用findClass(String)方法来查找类。
如果使用上述步骤找到了该类,并且解析标志为真,则此方法将在生成的Class对象上调用resolveClass(Class)方法。
鼓励ClassLoader 的子类覆盖findClass(String) ,而不是这个方法。
除非被覆盖,否则在整个类加载过程中,此方法会同步getClassLoadingLock方法的结果

注意此处的同步代码块

 ClassLoader类默认注册为具有并行能力。
 但是,如果它们具有并行能力,它的子类仍然需要注册自己。 
在委托模型不是严格分层的环境中,类加载器需要具有并行能力,
否则类加载会导致死锁,因为加载器锁在类加载过程中一直保持着

在看下上面源码的findclass方法,没有方法体,说明如果没有找到的话,会调用我们的自定义类加载器;这段内容得我们自己写;

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

既然说到这里了,我们怎么写自定义加载器呢

手写一个自定义类加载器

  1. 继承ClassLoader
  2. 重写findclass方法

我们这也重写了loadclass方法,可是没有调用重写的loadclass去加载,loadclass仅仅是获取二进制文件名(网上不建议重写该方法,我问公司大佬,他给的解释是自定义的动态加载方法是隔离状态的,类似于Tomcat的pandora(潘多拉),与项目本身不会冲突),类加载工作是通过_defineClass_来实现的所以最后我们还得调用该方法,_defineClass_主要签名该类,底层调用下面这个方法;

在我们日常开发中,这个遇到的最多的场景就是热加载,

打破的双亲委派

对于Tomcat,就打破了双亲委派,它是以沙箱的形式通过隔离来实现的;每个实例只会加载自己实例里的文件,JDBC也打破了双亲委派,关于jdbc的打破,查的资料是由于DriverManager.getconnection()会被根加载器加载,但是呢,但是里面的驱动比如说MYSQL、ORACLE的都是第三方的,而根加载器无法加载,所以引入下面代码的一段线程的上下文类加载器

@CallerSensitive
    public ClassLoader getContextClassLoader() {
        if (contextClassLoader == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                                   Reflection.getCallerClass());
        }
        return contextClassLoader;
    }

好啦,好啦,说的不是很深,如果感兴趣,可以自行再探索探索

觉得还行,点个赞再走嘛,交个朋友嘛