精华内容
下载资源
问答
  • 主要介绍了JVM的类加载过程以及双亲委派模型详解,类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。,需要的朋友可以参考下
  • 1.双亲委派模型的破坏(JDBC例子) https://blog.csdn.net/awake_lqh/article/details/106171219 2.面试官:说说双亲委派模型? https://baijiahao.baidu.com/s?id=1633056679004596814&wfr=spider&for=pc ...

     

    参考文章:

    1.双亲委派模型的破坏(JDBC例子)

    https://blog.csdn.net/awake_lqh/article/details/106171219

    2.面试官:说说双亲委派模型?

    https://baijiahao.baidu.com/s?id=1633056679004596814&wfr=spider&for=pc

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

    https://www.cnblogs.com/joemsu/p/9310226.html

     

       在我们的面试过程中,免不了会被问到 JVM 相关的知识。其中双亲委派模型 就是一个较为经常被考察的点。下面对这个点做一个整理。

     

    类的生命周期

      类从加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括, 加载,验证,准备,解析,初始化,使用 和 卸载 7个步奏。

     

    类的生命周期

     加载 -》 验证 -》准备 -》解析 -》初始化 -》使用  -》卸载

     

    以下5个阶段的顺序是确定的

     加载 -》验证 -》准备 -》 初始化 -》卸载

     

    类加载的全过程 5个阶段

     加载 -》 验证 -》准备 -》解析 -》初始化 

    其中 验证-》准备-》解析 又被统称为连接,所以 类加载又可以称为 加载,连接,初始化 3个阶段。

     

    验证

    验证阶段主要完成以下4个阶段的检验动作

    1.文件格式验证

    2.元数据验证

    3.字节码验证

    4.符号引用验证

     

    准备

     准备阶段正式为类变量分配内存,并设置类变量的初始值的阶段,这些变量所使用的内存都将是在方法去中进行分配的。

    Tips:

     1.这时候进行内存分配的仅包括类变量(被 static 修饰的变量)

     2.这里说的初始值 通常情况 下是 数据类型的零值,例如 public static int value = 123;  准备后的初始值为0,而不是123

     

    初始化的5种情况

       有且只有 以下5种情况, 必须立即对类进行 “初始化”。(而加载,验证,准备自然需要在此次之前开始)

    1.遇到 new, getstatic , putstatic 或 invokestatic 这 4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。

    2.使用 java.lang.reflect 包的方法对类 进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

    3.当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

    4.当虚拟机启动时,用户需要指定一个要执行的主类 (包含 main() 方法的哪个类),虚拟机会先初始化这个主类。

    5.当使用 JDK 1.7 的动态语言支持时,如果一个 Java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic , REF_putStatic, REF_invoke 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

    对 HotSpot 虚拟机,可以通过 -XX:+TraceClassLoading 参数观察此操作是否导致子类的加载。

     

     

    双亲委派模型

     

    类加载器引申的问题

         在Java中任意一个类都是由 这个类本身加载这个类的类加载器来确定 这个类在JVM中的唯一性。也就是你用你A类加载器加载的com.aa.ClassA 和 你B 类加载器加载的com.aa.ClassA它们是不同的,也就是用instanceof这种对比都是不同的。所以即使都来自于同一个class文件但是由不同类加载器加载的那就是两个独立的类。

     

    Java 的类加载器

      Java 提供了 3种类加载器 , 启动类加载器,扩展类加载器 和 应用程序类加载器

       启动类加载器 (Bootstrap ClassLoader ):  它是属于虚拟机自身的一部分,用C++实现的。 这个类加载器类 负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar 名字不符合的类库 即使放到lib 目录中也不会被加载)类库加载到虚拟机内存中。

     

      扩展类加载器(Extension ClassLoader): Java 实现的,独立于虚拟机,主要负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径的类库。开发者可以直接使用扩展类加载器。

     

       应用程序类加载器(Application ClassLoader)它是Java实现的,独立于虚拟机。主要负责加载用户类路径(classPath)上的类库,如果我们没有实现自定义的类加载器,  那 它 Application ClassLoader  就是我们程序中的默认加载器。

     

     

    类加载器的层次模型

    那么有那么多的类加载器,它们之前的层级关系是怎样的呢。它们之间是使用的双亲委派模型,如下图

     

     

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

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

    1. 子类先委托父类加载
    2. 父类加载器有自己的加载范围,范围内没有找到,则不加载,并返回给子类
    3. 子类在收到父类无法加载的时候,才会自己去加载

     

    双亲委派的实现

     双亲委派对保证Java 程序运行的稳定性很重要,实现却很简单,实现代码都在 java.lang.ClassLoader 的 loadClass() 方法中

    /**
         * Loads the class with the specified <a href="#name">binary name</a>.  The
         * default implementation of this method searches for classes in the
         * following order:
         *
         * <ol>
         *
         *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
         *   has already been loaded.  </p></li>
         *
         *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
         *   on the parent class loader.  If the parent is <tt>null</tt> the class
         *   loader built-in to the virtual machine is used, instead.  </p></li>
         *
         *   <li><p> Invoke the {@link #findClass(String)} method to find the
         *   class.  </p></li>
         *
         * </ol>
         *
         * <p> If the class was found using the above steps, and the
         * <tt>resolve</tt> flag is true, this method will then invoke the {@link
         * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
         *
         * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
         * #findClass(String)}, rather than this method.  </p>
         *
         * <p> Unless overridden, this method synchronizes on the result of
         * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
         * during the entire class loading process.
         *
         * @param  name
         *         The <a href="#name">binary name</a> of the class
         *
         * @param  resolve
         *         If <tt>true</tt> then resolve the class
         *
         * @return  The resulting <tt>Class</tt> object
         *
         * @throws  ClassNotFoundException
         *          If the class could not be found
         */
        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;
            }
        }
    

     

    双亲委派的一些思考

      1.Object 类复写,还是加载到 系统类的Object, 就是由双亲委派模型保证的。

     

     

     

    破坏双亲委派模型

       双亲委派模型有一系列的优势,还是需要去破坏双亲委派模型。比如 :基础类去调用回用户的代码。

    SPI 破坏双亲委派

      具体的例子:以Driver 接口为例,由于Driver 接口定义在JDK 当中,其实现由各个数据库的服务商来提供,比如 mysql 的就写了 MySQL Connector , 那么为题来了,DriverManager(也由JDK 提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,

      这里是通过 :线程上下文类加载器 Thread Context ClassLoader 实现的。这个类加载器可以通过 java.lang.Thread 类的 setContextClassLoaser() 方法进行设置,如果 创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有进行设置过的话,那这个类加载器默认就是应用程序类加载器。

      Java 中涉及SPI 的加载动作都采用这种方式,例如JNDI, JDBC,JCE,JAXB,JBI 等

    OSGi 热部署类加载机制 破坏双亲委派

        双亲委派第三次被破坏是基于程序动态性导致的,如代码热替换(HotSwap), 模块热部署 (Hot Deployment)

        目前OSGi 已经成了业界 事实上的 Java 模块化标准。其实先模块化热部署的关键是自定的类加载机制。每一个程序模块 (OSGi 称为 Bundle)都有一个自己的类加载器,当需要更换一个Bundle 时,就把 Bundle 连同类加载器一起替换掉以实现代码的热替换。

     

    JDBC 破坏双亲委派的例子

    package thread.classLoad;
    
    import java.sql.Connection;
    
    /**
     * Created by szh on 2020/6/15.
     */
    public class TestJDBC {
    
        public static void main(String[] args) throws Exception{
            String url = "jdbc:mysql://localhost:3306/testdb";
            Connection conn = java.sql.DriverManager.getConnection(url, "root", "root");
    
        }
    
    }

    追踪 getConnection 方法

        @CallerSensitive
        public static Connection getConnection(String url,
            String user, String password) throws SQLException {
            java.util.Properties info = new java.util.Properties();
    
            if (user != null) {
                info.put("user", user);
            }
            if (password != null) {
                info.put("password", password);
            }
    
            return (getConnection(url, info, Reflection.getCallerClass()));
        }
    

    追踪 getConnection 方法

     //  Worker method called by the public getConnection() methods.
        private static Connection getConnection(
            String url, java.util.Properties info, Class<?> caller) throws SQLException {
            /*
             * When callerCl is null, we should check the application's
             * (which is invoking this class indirectly)
             * classloader, so that the JDBC driver class outside rt.jar
             * can be loaded from here.
             */
            ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
            synchronized(DriverManager.class) {
                // synchronize loading of the correct classloader.
                if (callerCL == null) {
                    callerCL = Thread.currentThread().getContextClassLoader();
                }
            }
    
            if(url == null) {
                throw new SQLException("The url cannot be null", "08001");
            }
    
            println("DriverManager.getConnection(\"" + url + "\")");
    
            // Walk through the loaded registeredDrivers attempting to make a connection.
            // Remember the first exception that gets raised so we can reraise it.
            SQLException reason = null;
    
            for(DriverInfo aDriver : registeredDrivers) {
                // If the caller does not have permission to load the driver then
                // skip it.
                if(isDriverAllowed(aDriver.driver, callerCL)) {
                    try {
                        println("    trying " + aDriver.driver.getClass().getName());
                        Connection con = aDriver.driver.connect(url, info);
                        if (con != null) {
                            // Success!
                            println("getConnection returning " + aDriver.driver.getClass().getName());
                            return (con);
                        }
                    } catch (SQLException ex) {
                        if (reason == null) {
                            reason = ex;
                        }
                    }
    
                } else {
                    println("    skipping: " + aDriver.getClass().getName());
                }
    
            }
    
            // if we got here nobody could connect.
            if (reason != null)    {
                println("getConnection failed: " + reason);
                throw reason;
            }
    
            println("getConnection: no suitable driver found for "+ url);
            throw new SQLException("No suitable driver found for "+ url, "08001");
        }

     

     

    获取线程上下为类加载器

    callerCL = Thread.currentThread().getContextClassLoader();

    isDriverAllowed对于mysql连接jar进行加载

    isDriverAllowed(aDriver.driver, callerCL))

      isDriverAllowed将传入的Thread.currentThread().getContextClassLoader();拿到的应用类加载器用去Class.forName加载我们mysql连接jar,这样子就可以加载到我们自己的mtsql连接jar

        private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
            boolean result = false;
            if(driver != null) {
                Class<?> aClass = null;
                try {
                    aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
                } catch (Exception ex) {
                    result = false;
                }
    
                 result = ( aClass == driver.getClass() ) ? true : false;
            }
    
            return result;
        }


    为什么必须要破坏?

        DriverManager::getConnection 方法需要根据参数传进来的 url 从所有已经加载过的 Drivers 里找到一个合适的 Driver 实现类去连接数据库.
        Driver 实现类在第三方 jar 里, 要用 AppClassLoader 加载. 而 DriverManager 是 rt.jar 里的类, 被 BootstrapClassLoader 加载, DriverManager 没法用 BootstrapClassLoader 去加载 Driver 实现类(不再lib下), 所以只能破坏双亲委派模型, 用它下级的 AppClassLoader 去加载 Driver.
     

     

     

     

     

    展开全文
  • 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内核设计剖析》汪建,第十三章 公共与隔离的类加载器
    展开全文
  • 类加载器的层次结构: 三、什么是双亲委派模型 原理 :当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务...

    一、什么是类加载器

    类加载器就是把类文件(.class)加载到虚拟机中,也就是说通过一个类的全限定名来获取描述该类的二进制字节流。
    在这里插入图片描述

    二、类加载器分类

    1.启动(Bootstrap)类加载器:启动类加载器是用本地代码实现的类加载器,它负责将JAVA_HOME/lib下面的核心类库或-Xbootclasspath选项指定的jar包等虚拟机识别的类库加载到内存中。由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用。具体可由启动类加载器加载到的路径可通过System.getProperty(“sun.boot.class.path”)查看。

    2.扩展(Extension)类加载器:扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将JAVA_HOME/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器,具体可由扩展类加载器加载到的路径可通过System.getProperty(“java.ext.dirs”)查看。

    3.系统(System)类加载器:系统类加载器是由 Sun 的AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责将用户类路径(java-classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径,如第四节中的问题6所述)下的类库加载到内存中。开发者可以直接使用系统类加载器,具体可由系统类加载器加载到的路径可通过System.getProperty(“java.class.path”)查看。

    类加载器的层次结构图:
    在这里插入图片描述

    三、什么是双亲委派模型

    原理 :当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务时,才会尝试自己来加载。

    具体: 根据双亲委派模式,在加载类文件的时候,子类加载器首先将加载请求委托给它的父加载器,父加载器会检测自己是否已经加载过类,如果已经加载则加载过程结束,如果没有加载的话则请求继续向上传递直Bootstrap ClassLoader。如果请求向上委托过程中,如果始终没有检测到该类已经加载,则Bootstrap ClassLoader开始尝试从其对应路劲中加载该类文件,如果失败则由子类加载器继续尝试加载,直至发起加载请求的子加载器为止。

    四、为什么要使用双亲委派模型?

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

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

    或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String类,不去通过调用父加载器不就好了吗?确实,这样是可行。但是,在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型与待比较的类型的类加载器不同,那么会返回false。

    举个简单例子:
    ClassLoader1、ClassLoader2都加载java.lang.String类,对应Class1、Class2对象。那么Class1对象不属于ClassLoad2对象加载的java.lang.String类型。

    五、JDBC如何打破双亲委派模型

    参考博客:聊聊JDBC是如何破坏双亲委派机制的

    展开全文
  • 一文搞懂双亲委派模型

    万次阅读 多人点赞 2019-06-28 14:42:07
    由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。...

    类加载器

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

    从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:

    • 启动类加载器(Bootstrap ClassLoader),使用 C++ 实现,是虚拟机自身的一部分
    • 所有其它类的加载器,使用 Java 实现,独立于虚拟机,并且全部继承自抽象类 java.lang.ClassLoader。

    从 Java 开发人员的角度看,类加载器可以划分得更细致一些:

    • 启动类加载器(Bootstrap ClassLoader):前面已经大致介绍过了,这个类加载器负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
    • 扩展类加载器(Extension ClassLoader):这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
    • 应用程序类加载器(Application ClassLoader):这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

    我们的应用程序都是由上述这三种类加载器互相配合从而实现类加载,如果有必要,还可以加入自己定义的类加载器。
     

    双亲委派模型

    在这里插入图片描述
    上图是上面所介绍的这几种类加载器的层次关系,称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。

    一言概之,双亲委派模型,其实就是一种类加载器的层次关系
     

    1. 工作过程

    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。

    因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

    在分析类加载的源码时,我们还会再一次细致的提及类加载的过程。

    2. 好处

    • 使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。 例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。
    • 避免了多份同样字节码的加载,内存是宝贵的,没必要保存相同的两份 Class 对象,例如 System.out.println() ,实际我们需要一个 System 的 Class 对象,并且只需要一份,如果不使用委托机制,而是自己加载自己的,那么类 A 打印的时候就会加载一份 System 字节码,类 B 打印的时候又会加载一份 System 字节码。而使用委托机制就可以有效的避免这个问题。

    3. 实现

    双亲委派模型对于保证 Java 程序的稳定运作很重要,但它的实现却非常的简单,实现双亲委派的代码都集中在 java.lang.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) {
                    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.
                        c = findClass(name);
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
    
    

    从 loadClass() 的源码,我们再来看一下双亲委派模型的工作过程。它可以被下面这张图来概括。
    在这里插入图片描述
    主要可以分为两步:

    • 首先从底向上的检查类是否已经加载过,就是这行代码:Class<?> c = findLoadedClass(name);
    • 如果都没有加载过的话,那么就自顶向下的尝试加载该类。
    展开全文
  • 就是类加载器的划分与关系: 三、什么是双亲委派模型? 任何一个类都需要由加载它的“类加载器”和这个类本身一同确立其在JVM中的唯一性。但类的加载器有很多,为了确保这个类在JVM中的唯一性,这时候就要...
  • 首先说一下双亲委派的好处 双亲委派模型的好处: ·主要是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,比如String。·同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅...双亲委派模型图 ...
  • 双亲委派模型

    2018-07-30 23:13:05
    这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式!...
  • 双亲委派模型基本概念 定义: 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。 双亲委派模型的工作过程是: 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试...
  • 我们把目光向前移动,看到双亲委派模型的那幅图片,可以看出自定义的类加载器的关系是平行的,但是我这里只是一个自定义类加载器,所以我应该再去自定义一个类加载器来加载,这样它们才会为false吗?也不是,因为我...
  • 今天我们就来盘一盘这个面试题,不过在说双亲委派模型之前,我们得先简单了解下类加载。 类加载 我们平常写的代码是保存在一个 .java文件里面,经过编译会生成.class文件,这个文件存储的就是字节码,如果要用上我们...
  • 类从被加载到虚拟机中内存开始,到卸载除内存为止,它的生命周期包括如下所示: 上中的 加载,验证,准备,初始化,卸载这5个步骤的顺序是固定的,类的加载器也必须按这个顺序开始,而解析阶段则不一定,它...
  • 文章目录类加载器一、预定义类型类加载器二、类加载器结构双亲委派模型一、双亲委派模型流程二、双亲委派模型源码自定义类加载器一、类加载器继承关系二、ClassLoader1、构造函数2、核心方法三、自定义类加载器实例...
  • 从jvm中存在两种类加载器, 一个是启动类加载器,由c++语言实现,用来加载jre/lib/rt.jar 另一个就是其他类加载器,由java语言实现,继承自抽象...双亲委派模型的工作原理: 如果一个类加载器收到类加载的请求,它首先
  • 此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。 而有了双亲委派模型,黑客自定义的java.lang.String类永远都不会被加载进内存。因为...
  • 一、java类加载过程(双亲委派模型) 我们先来看一张图片,有助于理解类加载器与类加载过程。 概念 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接时候...
  • 今天介绍一下类加载过程和双亲委派模型。 概述 一般来说,Java类加载过程分为三个主要步骤:加载、链接、初始化。接下来,对这三个方面简要介绍一下: 1、加载 是Java将字节码数据从不同的数据源中读取到JV...
  • 双亲委派模型对于保证Java 程序的稳定性很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader 的 loadClass() 方法中:先检查类是否被家再过,若没有则调用父加载器的loadClass() 方法,...
  •  双亲委派模型有个显而易见的好处就是,Java类随着它的类加载器有了一个优先级的层次关系,比如放在rt.jar下的java.lang.Object 无论哪个类想加载它都会传递到启动类加载器这里,用户永远无法加载一个自己编写的与...
  • 【辰兮要努力】:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,...文章目录一、Java类加载器二、双亲委派模型 一、Java类加载器 首先引入一个概念什么是Java类加载器 一句话总结:类加载器(cl.
  • 我们都知道以 .java 结尾的 Java 源文件,经过编译之后会变成 .class 结尾的字节码文件。JVM 通过类加载器来加载字节码文件,然后再执行程序。...最后来在类加载的流程上,把双亲委派模型也添加上去。
  • JVM双亲委派模型

    2021-05-21 18:03:34
    文章目录 双亲委派原理 类加载器三个机制 双亲委派的意义 JVM提供的类加载器 执行类加载的五种方式 自定义类加载器 (1)遵守双亲委派模型:继承ClassLoader,重写findClass方法。 (2)破坏双亲委派模型:继承...
  • 浅谈双亲委派模型

    2021-02-25 19:39:44
    JDK版本:oracle java 1.8.0_102基本概念定义双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求...
  • 嘿嘿,有了线程上下文加载器,JNDI服务使用这个线程上下文加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,...
  • JDK为何自己首先破坏双亲委派模型?

    千次阅读 2021-01-19 20:35:49
    说是双亲,其实单亲,无奈迎合历史的错误翻译吧。 1 工作流程 当一个类加载器收到一个类加载请求 在 JDK9 后,会首先搜索它的内建加载器定义的所有“具名模块”: 如果找到合适的模块定义,将会使用该加载器来加载...
  • 类加载器种类在理解双亲委派模型之前,先来了解一下类加载器的种类。从Java虚拟机的角度来说,有两种不同的类加载器:启动类加载器和其它类加载器。启动类加载器在HotSpot虚拟机中使用C++语言实现,它是虚拟机的一...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,041
精华内容 6,016
关键字:

双亲委派模型图