精华内容
下载资源
问答
  • jvm 类加载机制 双亲委派模型 文档 jvm 类加载机制 双亲委派模型 文档
  • 双亲委派模型和破坏性双亲委派模型详解

    千次阅读 多人点赞 2019-06-17 12:28:45
    由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。...

    从JVM的角度来看,只存在两种类加载器:

    1. 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-X bootclasspath参数指定的路径中的类库加载到内存中。
    2. 其他类加载器:由Java语言实现,继承自抽象类ClassLoader。

     

    如:扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。

    应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

    1、双亲委派模型

    双亲委派模型工作过程

    如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

    简单的说:

    某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

    2、为什么使用双亲委派模型

    简单的来说:一个是安全性,另一个就是性能;(避免重复加载 和 避免核心类被篡改)

    用户自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。并且通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。

    而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

    3、破坏性双亲委派模型

    第一次破坏:

    由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。在此之前,用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法唯一逻辑就是去调用自己的loadClass()。

    第二次破坏:

    双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的同一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美。

    如果基础类又要调用回用户的代码,那该么办?

    一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,
    它的代码由启动类加载器去加载(在JDK1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者的代码,但启动类加载器不可能“认识”这些代码。

    为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

    有了线程上下文加载器,JNDI服务就可以使用它去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

    第三次破坏:

    双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换、模块热部署等,简答的说就是机器不用重启,只要部署上就能用。
    OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi幻境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当受到类加载请求时,OSGi将按照下面的顺序进行类搜索:
    1)将java.*开头的类委派给父类加载器加载。
    2)否则,将委派列表名单内的类委派给父类加载器加载。
    3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
    4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
    5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
    6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
    7)否则,类加载器失败。

    5、双亲委派模型破坏举例(JDBC,淘宝面试题)

    原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的mysql-connector-.jar中的Driver类具体实现的。 原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-.jar中的Driver类是用户自己写的代码,那启动类加载器肯定是不能进行加载的,既然是自己编写的代码,那就需要由应用程序启动类去进行类加载。于是乎,这个时候就引入线程上下文件类加载器(Thread Context ClassLoader)。有了这个东西之后,程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。

    展开全文
  • 主要介绍了JVM的类加载过程以及双亲委派模型详解,类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。,需要的朋友可以参考下
  • Java双亲委派模型是什么、优势在哪、双亲委派模型的破坏 前言 双亲委派模型是Java加载类的机制.采用双亲委派模型的好处是Java类随着它的类加载器一起具备了一种带有优先级的层级关系,通过这种层级关系可以避免类...

            Java双亲委派模型是什么、优势在哪、双亲委派模型的破坏

    前言

    双亲委派模型是Java加载类的机制.采用双亲委派模型的好处是Java类随着它的类加载器一起具备了一种带有优先级的层级关系,通过这种层级关系可以避免类的重复加载.

    • Bootstrap ClassLoader(启动类加载器): 负责将%JAVA_HOME%/lib目录中或-Xbootclasspath中参数指定的路径中的,并且是虚拟机识别的(按名称)类库加载到JVM中
    • Extension ClassLoader(扩展类加载器): 负责加载%JAVA_HOME%/lib/ext中的所有类库
    • Application ClassLoader(应用程序加载器): 负责ClassPath中的类库

    概述

    双亲委派模式的工作原理的是;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的双亲委派模式.那么这种模式有什么作用呢?

    双亲委派模式优势

        1 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。

        2 其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

     

    两种类型的类加载器详解

    1、 JVM自带的类加载器(3种):
    (1)根类加载器(Bootstrap):
    a、C++编写的,程序员无法在程序中获取该类
    b、负责加载虚拟机的核心库,比如java.lang.Object
    c、没有继承ClassLoader类
    (2)扩展类加载器(Extension):
    a、Java编写的,从指定目录中加载类库
    b、父加载器是根类加载器
    c、是ClassLoader的子类
    d、如果用户把创建的jar文件放到指定目录中,也会被扩展加载器加载。
    (3)系统加载器(System)或者应用加载器(App):
    a、Java编写的
    b、父加载器是扩展类加载器
    c、从环境变量或者class.path中加载类
    d、是用户自定义类加载的默认父加载器
    e、是ClassLoader的子类

    2、用户自定义的类加载器:
    (1)Java.lang.ClassLoader类的子类
    (2)用户可以定制类的加载方式
    (3)父类加载器是系统加载器
    (4)编写步骤:
    A、继承ClassLoader
    B、重写findClass方法。从特定位置加载class文件,得到字节数组,然后利用defineClass把字节数组转化为Class对象
    (5)为什么要自定义类加载器?
    A、可以从指定位置加载class文件,比如说从数据库、云端加载class文件
    B、加密:Java代码可以被轻易的反编译,因此,如果需要对代码进行加密,那么加密以后的代码,就不能使用Java自带的ClassLoader来加载这个类了,需要自定义ClassLoader,对这个类进行解密,然后加载。

     

    源码

    1.首先加载类调用的loadClass方法,我们找到ClassLoader的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;
            }
        }复制代码

     

    • 首先判断了该类是否已加载.
    • 若没加载,则传给双亲加载器去加载,
    • 若双亲加载器没能成功加载它,则自己用findClass()去加载.所以是个向上递归的过程.
    • 自定义加载器时,需要重写findClass方法,因为是空的,没有任何内容:
     protected Class<?> findClass(String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }复制代码

     


    双亲委派模型的破坏

    1.第一次破坏

    由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。在此之前,用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法,因为虚拟机在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法唯一逻辑就是去调用自己的loadClass()。

    2.第二次破坏

    双亲委派模型的第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的同一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美。

    如果基础类又要调用回用户的代码,那该么办?

    一个典型的例子就是JNDI服务,JNDI现在已经是Java的标准服务,
    它的代码由启动类加载器去加载(在JDK1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者的代码,但启动类加载器不可能“认识”这些代码。

    为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。

    有了线程上下文加载器,JNDI服务就可以使用它去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

    3.第三次破坏

    双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换、模块热部署等,简答的说就是机器不用重启,只要部署上就能用。
    OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi幻境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当受到类加载请求时,OSGi将按照下面的顺序进行类搜索:
    1)将java.*开头的类委派给父类加载器加载。
    2)否则,将委派列表名单内的类委派给父类加载器加载。
    3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
    4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
    5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
    6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
    7)否则,类加载器失败。

    自己编写

    1.首先需要一个编译好的class文件,笔者用了一个之前写的斐波那契的类Fib.class(所在路径:C:/Users/Think/crabapple),下面是用idea通过反编译方式打开的class文件,注意记下class文件的包名,在后续代码中需要使用类的全限定名称.

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    package crabapple;
     
    public class Fib {
      
        public static int fib(int num) {
            return num < 2 ? num : fib(num - 2) + fib(num - 1);
        }
    }复制代码

    2.继承ClassLoader,重写findClass方法:

    class MyClassLoader extends ClassLoader {
        private String classPath;  // 保存的地址
     
        /**
         * 传入地址构造函数
         * @param classPath
         */
        public MyClassLoader(String classPath) {
            this.classPath = classPath;
        }
     
        /**
         * 读取class文件
         * @param name
         * @return
         * @throws Exception
         */
        private byte[] loadByte(String name) throws Exception {
            String inPath = classPath + "/" + name + ".class";
            FileInputStream fis = new FileInputStream(inPath);
            int len = fis.available();
            byte[] data = new byte[len];
            fis.read(data);
            fis.close();
            return data;
        }
     
        /**
         * 重写findClass方法,让加载的时候调用findClass方法
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] data = loadByte(name);
                // 将字节码载入内存
                return defineClass(name, data, 0, data.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }复制代码
    • loadByte方法仅用作读取文件
    • findClass方法才是加载类到内存的,注意name必须填全限定名,比如java.lang.Object.

     3.测试,一下将使用一些反射机制和class类的方法

    public class ClassLoaderTest extends ClassLoader {
     
        //main函数本该抛出异常有 ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException,为了好看,简写成Exception
        public static void main(String[] args) throws Exception {
            //初始化类加载器
            MyClassLoader myClassLoader=new MyClassLoader("C:/Users/Think/crabapple");
            //加载Fib类,笔者class文件包名为crabapple
            Class myClass=myClassLoader.loadClass("crabapple.Fib");
            //获取加载类的实例
            Object object=myClass.newInstance();
            //获取该类一个名为fib,且参数为int的方法
            Method method=myClass.getMethod("fib",int.class);
            //执行这个方法
            int result=method.invoke(object,4);
            //打印结果
            System.out.print(result);
            
            //output
            /**
             * 3
             * Process finished with exit code 0
             */
        }
    }复制代码
    • 执行成功
    • 我们来分析下,Fib类的加载过程,初始化自定义类加载器后,loadClass方法肯定将其委派到双亲Application ClassLoader,而Application ClassLoader又将其委派到Extension ClassLoader,继而委派到Bootstrap ClassLoader.但是Bootstrap ClassLoader发现Fib并不在自己的加载能力范围内,于是移向Extension ClassLoader,同理Extension ClassLoader只能加载/ext中的class,继而让给Application ClassLoader,而Application ClassLoader只加载classpath中的类,于是又回到我们自定义的MyClassLoader,幸好我们重写了findClass方法进而执行了加载,否在findClass抛出找不到类的异常.至此Fib类加载完成.

     参考链接

    https://blog.csdn.net/weixin_34010566/article/details/91404531

    https://blog.csdn.net/codeyanbao/article/details/82875064

    https://mp.weixin.qq.com/s/H3ZGI9XIwrWGsByKbCYC7A

    展开全文
  • 1. 双亲委派就是类加载器之间的层级关系,加载类的过程是一个递归调用的过程,首先一层一层向上委托父类加载器加载,直到到达最顶层启动类加载器,启动类加载器无法加载时,再一层一层向下委托给子类加载器加载。...

    一、前言

    平时做业务开发比较少接触类加载器,但是如果想深入学习Tomcat、Spring等开源项目,或者从事底层架构的开发,了解甚至熟悉类加载的原理是必不可少的。

    java的类加载器有哪些?什么是双亲委派?为什么要双亲委派?如何打破它?多多少少对这些概念了解一些,甚至因为应付面试背过这些知识点,但是再深入一些细节,却知之甚少。

    二、类加载器

    类加载器,顾名思义就是一个可以将Java字节码加载为java.lang.Class实例的工具。这个过程包括,读取字节数组、验证、解析、初始化等。另外,它也可以加载资源,包括图像文件和配置文件。

    类加载器的特点:

    • 动态加载,无需在程序一开始运行的时候加载,而是在程序运行的过程中,动态按需加载,字节码的来源也很多,压缩包jar、war中,网络中,本地文件等。类加载器动态加载的特点为热部署,热加载做了有力支持。
    • 全盘负责,当一个类加载器加载一个类时,这个类所依赖的、引用的其他所有类都由这个类加载器加载,除非在程序中显式地指定另外一个类加载器加载。所以破坏双亲委派不能破坏扩展类加载器以上的顺序。

    一个类的唯一性由加载它的类加载器和这个类的本身决定(类的全限定名+类加载器的实例ID作为唯一标识)。比较两个类是否相等(包括Class对象的equals()isAssignableFrom()isInstance()以及instanceof关键字等),只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,这两个类就必定不相等。

    从实现方式上,类加载器可以分为两种:一种是启动类加载器,由C++语言实现,是虚拟机自身的一部分;另一种是继承于java.lang.ClassLoader的类加载器,包括扩展类加载器应用程序类加载器以及自定义类加载器。

    启动类加载器Bootstrap ClassLoader):负责加载<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果想设置Bootstrap ClassLoader为其parent可直接设置null

    扩展类加载器Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定路径中的所有类库。该类加载器由sun.misc.Launcher$ExtClassLoader实现。扩展类加载器由启动类加载器加载,其父类加载器为启动类加载器,即parent=null

    应用程序类加载器Application ClassLoader):负责加载用户类路径(ClassPath)上所指定的类库,由sun.misc.Launcher$App-ClassLoader实现。开发者可直接通过java.lang.ClassLoader中的getSystemClassLoader()方法获取应用程序类加载器,所以也可称它为系统类加载器。应用程序类加载器也是启动类加载器加载的,但是它的父类加载器是扩展类加载器。在一个应用程序中,系统类加载器一般是默认类加载器。

    三、双亲委派机制

    1、什么是双亲委派

    JVM 并不是在启动时就把所有的.class文件都加载一遍,而是程序在运行过程中用到了这个类才去加载。除了启动类加载器外,其他所有类加载器都需要继承抽象类ClassLoader,这个抽象类中定义了三个关键方法,理解清楚它们的作用和关系非常重要。

    public abstract class ClassLoader {
    
        //每个类加载器都有个父加载器
        private final ClassLoader parent;
        
        public Class<?> loadClass(String name) {
      
            //查找一下这个类是不是已经加载过了
            Class<?> c = findLoadedClass(name);
            
            //如果没有加载过
            if( c == null ){
              //先委派给父加载器去加载,注意这是个递归调用
              if (parent != null) {
                  c = parent.loadClass(name);
              }else {
                  // 如果父加载器为空,查找Bootstrap加载器是不是加载过了
                  c = findBootstrapClassOrNull(name);
              }
            }
            // 如果父加载器没加载成功,调用自己的findClass去加载
            if (c == null) {
                c = findClass(name);
            }
            
            return c;
        }
        
        protected Class<?> findClass(String name){
           //1. 根据传入的类名name,到在特定目录下去寻找类文件,把.class文件读入内存
              ...
              
           //2. 调用defineClass将字节数组转成Class对象
           return defineClass(buf, off, len)}
        
        // 将字节码数组解析成一个Class对象,用native方法实现
        protected final Class<?> defineClass(byte[] b, int off, int len){
           ...
        }
    }
    

    从上面的代码可以得到几个关键信息:

    • JVM 的类加载器是分层次的,它们有父子关系,而这个关系不是继承维护,而是组合,每个类加载器都持有一个 parent字段,指向父加载器。(AppClassLoaderparentExtClassLoaderExtClassLoaderparentBootstrapClassLoader,但是ExtClassLoaderparent=null。)
    • defineClass方法的职责是调用 native 方法把 Java 类的字节码解析成一个 Class 对象。
    • findClass方法的主要职责就是找到.class文件并把.class文件读到内存得到字节码数组,然后调用 defineClass方法得到 Class 对象。子类必须实现findClass
    • loadClass方法的主要职责就是实现双亲委派机制:首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则委派给父加载器加载,这是一个递归调用,一层一层向上委派,最顶层的类加载器(启动类加载器)无法加载该类时,再一层一层向下委派给子类加载器加载

    双亲委派模型

    2、为什么要双亲委派?

    双亲委派保证类加载器,自下而上的委派,又自上而下的加载,保证每一个类在各个类加载器中都是同一个类。

    一个非常明显的目的就是保证java官方的类库<JAVA_HOME>\lib和扩展类库<JAVA_HOME>\lib\ext的加载安全性,不会被开发者覆盖。

    例如类java.lang.Object,它存放在rt.jar之中,无论哪个类加载器要加载这个类,最终都是委派给启动类加载器加载,因此Object类在程序的各种类加载器环境中都是同一个类。

    如果开发者自己开发开源框架,也可以自定义类加载器,利用双亲委派模型,保护自己框架需要加载的类不被应用程序覆盖。

    四、破坏双亲委派

    如果想自定义类加载器,就需要继承ClassLoader,并重写findClass,如果想不遵循双亲委派的类加载顺序,还需要重写loadClass。如下是一个自定义的类加载器,并重写了loadClass破坏双亲委派:

    package com.stefan.DailyTest.classLoader;
    
    import java.io.*;
    
    public class TestClassLoader extends ClassLoader {
        public TestClassLoader(ClassLoader parent) {
            super(parent);
        }
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 1、获取class文件二进制字节数组
            byte[] data = null;
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                FileInputStream fis = new FileInputStream(new File("C:\\study\\myStudy\\JavaLearning\\target\\classes\\com\\stefan\\DailyTest\\classLoader\\Demo.class"));
                byte[] bytes = new byte[1024];
                int len = 0;
                while ((len = fis.read(bytes)) != -1) {
                    baos.write(bytes, 0, len);
                }
                data = baos.toByteArray();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 2、字节码数组加载到 JVM 的方法区,
            // 并在 JVM 的堆区建立一个java.lang.Class对象的实例
            // 用来封装 Java 类相关的数据和方法
            return this.defineClass(name, data, 0, data.length);
        }
    
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException{
            // 1、找到ext classLoader,并首先委派给它加载,为什么?
            ClassLoader classLoader = getSystemClassLoader();
            while (classLoader.getParent() != null) {
                classLoader = classLoader.getParent();
            }
            Class<?> clazz = null;
            try {
                clazz = classLoader.loadClass(name);
            } catch (ClassNotFoundException e) {
                // Ignore
            }
            if (clazz != null) {
                return clazz;
            }
            // 2、自己加载
            clazz = this.findClass(name);
            if (clazz != null) {
                return clazz;
            }
            // 3、自己加载不了,再调用父类loadClass,保持双亲委派模式
            return super.loadClass(name);
        }
    }
    

    破坏双亲委派

    测试加载Demo类:

    package com.stefan.DailyTest.classLoader;
    
    public class Test {
        public static void main(String[] args) throws Exception {
            // 初始化TestClassLoader,并将加载TestClassLoader类的类加载器
            // 设置为TestClassLoader的parent
            TestClassLoader testClassLoader = new TestClassLoader(TestClassLoader.class.getClassLoader());
            System.out.println("TestClassLoader的父类加载器:" + testClassLoader.getParent());
            // 加载 Demo
            Class clazz = testClassLoader.loadClass("com.stefan.DailyTest.classLoader.Demo");
            System.out.println("Demo的类加载器:" + clazz.getClassLoader());
        }
    }
    
    //控制台打印
    TestClassLoader的父类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
    Demo的类加载器:com.stefan.DailyTest.classLoader.TestClassLoader@78308db1
    

    注意破坏双亲委派的位置,自定义类加载机制先委派给ExtClassLoader加载,ExtClassLoader再委派给BootstrapClassLoader,如果都加载不了,然后自定义类加载器加载,自定义类加载器加载不了才交给AppClassLoader。为什么不能直接让自定义类加载器加载呢?

    不能!双亲委派的破坏只能发生在AppClassLoader及其以下的加载委派顺序,ExtClassLoader上面的双亲委派是不能破坏的!

    因为任何类都是继承自超类java.lang.Object,而加载一个类时,也会加载继承的类,如果该类中还引用了其他类,则按需加载,且类加载器都是加载当前类的类加载器。

    Demo类只隐式继承了Object,自定义类加载器TestClassLoader加载了Demo,也会加载Object。如果loadClass直接调用TestClassLoaderfindClass会报错java.lang.SecurityException: Prohibited package name: java.lang

    为了安全,java是不允许除BootStrapClassLOader以外的类加载器加载官方java.目录下的类库的。在defineClass源码中,最终会调用native方法defineClass1获取Class对象,在这之前会检查类的全限定名name是否是java.开头。(如果想完全绕开java的类加载,需要自己实现defineClass,但是因为个人能力有限,没有深入研究defineClass的重写,并且一般情况也不会破坏ExtClassLoader以上的双亲委派,除非不用java了。)

    defineClass

    preDefineClass
    通过自定义类加载器破坏双亲委派的案例在日常开发中非常常见,比如Tomcat为了实现web应用间加载隔离,自定义了类加载器,每个Context代表一个web应用,都有一个webappClassLoader。再如热部署、热加载的实现都是需要自定义类加载器的。破坏的位置都是跳过AppClassLoader

    五、Class.forName默认使用的类加载器

    1. forName(String name, boolean initialize,ClassLoader loader)可以指定classLoader
    2. 不显式传classLoader就是默认当前类的类加载器:
    public static Class<?> forName(String className)
                    throws ClassNotFoundException {
          Class<?> caller = Reflection.getCallerClass();
          return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
    

    六、线程上下文类加载器

    线程上下文类加载器其实是一种类加载器传递机制。可以通过java.lang.Thread#setContextClassLoader方法给一个线程设置上下文类加载器,在该线程后续执行过程中就能把这个类加载器取(java.lang.Thread#getContextClassLoader)出来使用。

    如果创建线程时未设置上下文类加载器,将会从父线程(parent = currentThread())中获取,如果在应用程序的全局范围内都没有设置过,就默认是应用程序类加载器。

    线程上下文类加载器的出现就是为了方便破坏双亲委派:

    一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK 1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能去加载ClassPath下的类。

    但是有了线程上下文类加载器就好办了,JNDI服务使用线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。

    Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

    摘自《深入理解java虚拟机》周志明

    七、要点回顾

    1. java 的类加载,就是获取.class文件的二进制字节码数组并加载到 JVM 的方法区,并在 JVM 的堆区建立一个用来封装 java 类相关的数据和方法的java.lang.Class对象实例。
    2. java默认有的类加载器有三个,启动类加载器(BootstrapClassLoader),扩展类加载器(ExtClassLoader),应用程序类加载器(也叫系统类加载器)(AppClassLoader)。类加载器之间存在父子关系,这种关系不是继承关系,是组合关系。如果parent=null,则它的父级就是启动类加载器。启动类加载器无法被java程序直接引用。
    3. 双亲委派就是类加载器之间的层级关系,加载类的过程是一个递归调用的过程,首先一层一层向上委托父类加载器加载,直到到达最顶层启动类加载器,启动类加载器无法加载时,再一层一层向下委托给子类加载器加载。
    4. 双亲委派的目的主要是为了保证java官方的类库<JAVA_HOME>\lib和扩展类库<JAVA_HOME>\lib\ext的加载安全性,不会被开发者覆盖。
    5. 破坏双亲委派有两种方式:第一种,自定义类加载器,必须重写findClassloadClass;第二种是通过线程上下文类加载器的传递性,让父类加载器中调用子类加载器的加载动作。

    参考:

    • 《深入理解java虚拟机》周志明(书中对类加载的介绍非常详尽,部分精简整理后引用。)
    • 《深入拆解Tomcat & Jetty》Tomcat如何打破双亲委托机制?李号双
    • 《Tomcat内核设计剖析》汪建,第十三章 公共与隔离的类加载器
    展开全文
  • 双亲委派模型

    2021-04-14 23:00:00
    有了线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的 SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经...

    虚拟机设计团队把类加载阶段中的 “通过一个类的全限定名来获取描述此类的二进制字节流” 这个动作放到 Java虚拟机外部去实现,以便应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。

    从 Java 虚拟机的角度上,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其它所有的类加载器,这些类加载器都由 Java语言实现,独立于虚拟机外部,并且全部继承自 java.lang.ClassLoader

    从Java开发人员的角度看,类加载器还可以划分得更细一些,如下:
    【1】启动类加载器(Bootstrap ClassLoader)这个类加载器负责将放置在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定路径中的,并且是虚拟机能识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放置在 lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接使用。
    【2】扩展类加载器(Extension ClassLoader)这个类加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
    【3】应用程序类加载器(Application ClassLoader)这个类加载器由sum.misc.Launcher.$AppClassLoader来实现。由于这个类加载器是 ClassLoader中的 getSystemClassLoader()方法的返回值,所以一般也被称为系统类加载器。它负责加载用户类路径上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    应用程序由这三种类加载器互相配合进行加载的,如果有必须,还可以加入自己定义的类加载器。这些类加载器之间的关系一般如下图:

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

    使用双亲委派模型来组织类加载器的一个好处就是 Java类因类加载器具备了一种带有优先级的层次关系。例如 Object类,他存放在 rt.jar 之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。双亲委派模型对于保证 Java程序的稳定运作很重要,但它的实现非常简单,实现双亲委派代码都集中在 java.lang.ClassLoader 的 loadClass() 方法中,如下,逻辑简单清晰,先检查是否已经被加载过,若没有加载则调用父加载器的 loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出 ClassNotFoundException 异常后,在调用自己的 findClass() 方法进行加载

    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;
        }
    
        // findClass 直接抛出 ClassNotFoundException
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }
    }

    破坏双拼委派模型

    双亲委派模型并不是一个强制性的约束模型,而是 Java设计者推荐给开发者的类加载器实现方式。目前为止,双亲委派的具体逻辑就实现在 loadClass方法之中。应当把自己的类加载逻辑写到 findClass() 方法中,在 loadClass() 方法的逻辑里如果父类加载失败,则会调用自己的 findClass() 方法完成加载,这样就保证了新写出来的类加载器是否符合双亲委派规则。

    如果基础类要调用用户的代码,那该怎么办呢。这并非是不可能的事情,一个典型的例子便是 JNDI服务,它的代码由启动类加载器去加载(在JDK1.3时放进rt.jar),但 JNDI的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现部署在应用程序的 classpath下的 JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识”之些代码,该怎么办?
    为了解决这个困境,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过 java.lang.Thread类的 setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。有了线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的 SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。Java中所有涉及 SPI的加载动作基本上都采用这种方式,例如 JNDI,JDBC,JCE,JAXB和JBI等。
    双亲委派模型的第三次“被破坏”是由于用户对程序的动态性的追求导致的,例如 OSGi的出现。在 OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构。

    展开全文
  • 1.双亲委派模型的破坏(JDBC例子) https://blog.csdn.net/awake_lqh/article/details/106171219 2.面试官:说说双亲委派模型? https://baijiahao.baidu.com/s?id=1633056679004596814&wfr=spider&for=pc ...
  • 类加载器的层次结构图: 三、什么是双亲委派模型 原理 :当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务...
  • 双亲委派模型详解

    千次阅读 2019-11-01 08:45:57
    双亲委派模型 在介绍这个Java技术点之前,先试着思考以下几个问题: 为什么我们不能定义同名的String的 java 文件? 多线程的情况下,类的加载为什么不会出现重复加载的情况? 热部署的原理是什么? ...
  • 类加载器在加载阶段,会将class文件加载进方法区。有关类加载的全过程,可以先参考我的另外一篇文章类的奇幻漂流——类加载机制探秘 类加载器的类型 类加载器有以下种类: 启动类加载器(Bootstrap ClassLoader) ...
  • 【辰兮要努力】:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,...文章目录一、Java类加载器二、双亲委派模型 一、Java类加载器 首先引入一个概念什么是Java类加载器 一句话总结:类加载器(cl.
  • 文章目录双亲委派模型什么是双亲委派模型分类工作流程作用破坏双亲委派模型三大特性自定义类加载器 双亲委派模型 什么是双亲委派模型 双亲委派模型是指除了顶层的启动类加载器之外,其他的类加载器都有自己的父类...
  • JVM双亲委派模型 本文有十下亲自书写,资料源自网络,内容由自己的总结和理解产生,禁止搬运! 一、先谈类加载器 什么是类加载器 Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二...
  • SPI如何破坏双亲委派模型 # 双亲委派# SPI 类加载器(classloader) 先从类加载器说起,凡事先问是什么,首先什么是类加载器? 我们知道,一个 *.java 的代码源文件要执行起来之前,必须通过 javac 构建抽象...
  • 学习目标 双亲委派模型的⼯作机制。 类加载器的分类及各⾃的职责。 双亲委派模型的好处。 打破双亲委派模型的三种场景。 线程上下⽂类加载器在是如何实现 SPI 的。 ...
  • ** 一、基本概念 一个类是由加载它的类加载器和这个类本身来共同确定其在Java虚拟机中的唯一性。...双亲委派模型要求除了最顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,在双亲委派模型中...
  • 由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则是JDK1.0时候就已经存在,面对已经存在 的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。...
  • 面试被问及双亲委派模型,虽然对双亲委派模型有大致的了解,但是还是被问到一脸懵逼,所以记录下双亲委派模型对的基本概念、实现原理等 基本概念 定义 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都...
  • 我们都知道类加载的双亲委派模型 双亲委派模型并不是一个强制约束模型,而是java设计者推荐给开发者的类加载实现方式;但是也会有例外; 今天我们主要来讲一讲 类似于SPI这种设计导致的双亲委派模型被“破坏”的情况;...
  • 一文搞懂双亲委派模型

    万次阅读 多人点赞 2019-06-28 14:42:07
    由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。...
  • 1什么是双亲委派模型? https://blog.csdn.net/zs18753479279/article/details/114497506 2如何破坏双亲委派模型? 典型的两个方法:1自定义类加载器,重写loadClass方法;2使用线程上下文类加载器; 1 重写load...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 25,752
精华内容 10,300
关键字:

双亲委派模型