精华内容
下载资源
问答
  • 如何打破双亲委派机制
    千次阅读
    2021-02-21 22:57:28

    双亲委派机制

           第一次知道何为打破双亲委派机制是通过阅读周志明的《深入理解Java虚拟机》,我们知道双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。【这里的“父类”只是名义上的父类,而不是真的是继承上父子关系】
           这种模型要求,除了顶层的启动类加载器外,其他的类加载器都要有自己的父类加载器。假如有一个类要加载进来,一个类加载器并不会马上尝试自己将其加载,而是委派给父类加载器,父类加载器收到后又尝试委派给其父类加载器,以此类推,直到委派给启动类加载器,这样一层一层往上委派。只有当父类加载器反馈自己没法完成这个加载时,子加载器才会尝试自己加载。通过这个机制,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,同时这个机制也保证了安全性。设想如果应用程序类加载器想要加载一个有破坏性的 java.lang.System 类,双亲委派模型会一层层向上委派,最终委派给启动类加载器,而启动类加载器检查到缓存中已经有了这个类,并不会再加载这个有破坏性的 System 类。
           另外,类加载器还拥有全盘负责机制,即当一个类加载器加载一个类时,这个类所依赖的、引用的其他所有类都由这个类加载器加载,除非在程序中显式地指定另外一个类加载器加载。
           在 Java 中,我们用完全匹配类名来标识一个类,即用包名和类名。而在 JVM 中,一个类由完全匹配类名和一个类加载器的实例 ID 作为唯一标识。也就是说,同一个虚拟机可以有两个包名、类名都相同的类,只要它们由两个不同的类加载器加载。当我们在 Java 中说两个类是否相等时,必须在针对同一个类加载器加载的前提下才有意义,否则,就算是同样的字节码,由不同的类加载器加载,这两个类也不是相等的。这种特征为我们提供了隔离机制,在 Tomcat 服务器中就使用到了这样的隔离机制。

    在书中我们可以知道双亲委派模型破坏历史,以下是从书上摘抄下来

    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将按照下面的顺序进行类搜索:
      ① 将java.*开头的类委派给父类加载器加载。
      ② 否则,将委派列表名单内的类委派给父类加载器加载。
      ③ 否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
      ④ 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载。
      ⑤ 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
      ⑥ 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
      ⑦ 否则,类加载器失败。

    jvm自带的三个类加载器所加载的路径如下

    • Bootstrap ClassLoader————jdk/jre/lib/ 目录下 Extension

    • ClassLoader————jdk/jre/lib/ext/ 目录下

    • Application ClassLoader————ClassPath路径下

    双亲委派工作流程
    ①、当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。
    ②、当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。
    ③、如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。
    ④、如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。
    ⑤、如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载。
    ⑥、如果均加载失败,就会抛出ClassNotFoundException异常。

    应用场景

    那么那些地方会应用到打破双亲委派机制这样的情况呢?
    答:

    1. SPI机制:对应双亲委派破坏使的第二次
      例如:
           ①JDBC加载不同类型的驱动模块:
            以DriverManager.class来说明。DriverManager的classloader是BootstrapClassLoader,而其得到的connection的classloader是ApplicationClassLoader。如果在A类引用B类时发现B类还没有加载,那么会调用A类的类加载器进行加载,并且由于可见性的原因,BootstrapClassLoader加载的类是看不到Extension ClassLoader或者ApplicationClassLoader加载的类的,这里对应的是DriverManager和connection。所以说SPI破坏了双亲委任模型。
           若还没理解的话我在详细解释一下,若按照双亲委任模型的话, DriverManager被Bootstrap类加载器加载,其内部引用到的connection也理应由Bootstrap类加载器加载,但Driver.class的实现类不在Bootstrap类加载器所能扫描到的范围里,所以交由Bootstrap类加载器是弄不了的,而双亲委任模型规定是先交由父类去加载,加载不了再由自己加载,而Bootstrap类加载器是最顶层的类加载器了,没有父类了,所以交由自己加载,但是自己也加载不了,所以为了能够加载到Driver.class的实现类,我们只能打破双亲委任模型了【使用子类加载器去加载Driver.class的实现类】,通过Thread.currentThread().getContextClassLoader()的方式,我们将得到ApplicationClassLoader,而ApplicationClassLoader就能够扫描到我们添加进来的jar包【依赖的jar包都是在ClassPath下】,所以Driver.class的实现类就能够被加载到。若仍然还未能理解的话,请看JDBC加载mysql驱动模块
    2. tomcat:在tomcat中,核心的Java类的加载还是遵从双亲委派模型的 ,而重点突出违反双亲委派模型的是:在Tomcat中各个web应用在加载类的时候会优先使用自己的类加载器(WebAppClassLoader),加载不到时再交给commonClassLoader走双亲委托 ,这就打破了双亲委派机制。
    3. 热部署:可以看上面双亲委派破坏史第三次
    更多相关内容
  • 双亲委派机制以及打破双亲委派机制 双亲委派机制 Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存中生成class对象,而且加载某个类的class文件时,Java虚拟机...

    双亲委派机制以及打破双亲委派机制

    双亲委派机制

    Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存中生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式.

    双亲委派机制的优点:

    • 1、避免类的重复加载
    • 2、保护程序的安全性,防止核心API随意被篡改
      在这里插入图片描述
      在这里插入图片描述

    沙箱安全机制:保证对Java信息源代码的保护

    何为沙箱安全机制?即如果我们自己建立和源代码相同的包,例如java/lang/String.Class,在我们去使用类加载器去加载此类时,为了防止你自定义的类对源码的破坏,所以他默认不是使用你的String类的本身的系统加载器去加载它,而是选择率先使用引导类加载器去加载,而引导类在加载的过程中会先去加载JDK自带的文件(rt.jar包中的java/lang/String.class),而不是你自己定义的String.class,报错信息会提示没有main方法 ,就是因为加载的是rt.jar包下的String类,这样就可以做到保证对java核心源代码的保护,这即是沙箱保护机制.

    那么双亲委派机制存在哪些缺点,又有哪些打破双亲委派机制的例子呢?

    通过上面双亲委派机制的特点,我们知道了以下结论:

    由于BootStrapClassLoader是顶级类加载器,并且它不能使用AppClassLoader加载器加载的类,因为BootstrapClassloader无法委派AppClassLoader来加载类。故当我们的上层类加载器需要下层类加载器帮忙加载类,就会出现问题,这便是双亲委派机制的缺点,那么哪些场景发生了这些事情,而Java又是如何打破这种双亲委派机制的呢?

    我们来举一个经典的打破双亲委派机制的例子:Java SPI机制

    SPI(Service Provider Interface),主要是应用于厂商自定义组件或插件中。

    java SPI提供一种服务发现的机制,即为某个接口寻找服务实现的机制,我们在需要在jar包的META-INF/services/目录里面创建一个一服务接口命令的文件,而该文件就是实现这个接口的具体实现类,当有外部程序需要装配我们的这个模块接口的时候,就可以通过META-INF/services/目录的配置文件找到对应的实现类的类名,并将其实例化,完成该接口模块的注入。而在JDK中,也提供了一个服务实现查找的一个工具类:java.util.ServiceLoader。

    我们以数据库连接jar包为例:mysql-connector-java-5.1.37.jar,它遵循SPI的规范

    在这里插入图片描述

    我们的java.sql.Driver接口定义在java.sql包下,而该包的位置位于jdk\jre\lib\rt.jar,其中java.sql包中还提供了其它相应的类和接口比如管理驱动的类:DriverManager类等等,这样我们就很清楚地知道了java.sql是由BootStrapClassLoader加载器加载的

    而我们的接口的实现类com.mysql.jdbc.Driver是第三方实现的类库,是由AppClassLoader加载器加载的

    那么问题来了:我们使用DriverManager获取Connection的时候,必然会加载到com.mysql.jdbc.Driver类,此时即是BootstrapClassloader加载的类使用了由AppClassLoader加载的类,很明显和双亲委托机制的原理相悖!!!

    JVM采取了一种作弊的方式打破了双亲委派机制,从而完成了上层类加载器使用下层类加载器进行加载的类:

    在BootstrapClassLoader或ExtClassLoader加载的类A中,如果使用到AppClassLoader类加载器加载的类B,由于双亲委托机制不能向下委托,那可以在类A中通过线程上下文类加载器获得AppClassLoader,从而去加载类B

    我们通过源码来看一下:

    首先我们创建一个web项目,然后导入mysql的驱动包,我们编写一下测试代码:

    public class TestClassLoader {
        public static void main(String[] args) throws SQLException, ClassNotFoundException {
            String driverClassName = "com.mysql.jdbc.Driver";
            String url = "jdbc:mysql://localhost:3306/mybatis";
            String username = "root";
            String password = "root";
            Connection connection = null;
            PreparedStatement pstmt = null;
            ResultSet rs = null;
    
            //执行驱动"com.mysql.jdbc.Driver"
            Class.forName("com.mysql.jdbc.Driver");
    
            //获取连接
            connection = DriverManager.getConnection(url,username,password);
        }
    }
    

    此时我们知道这段代码肯定是能正常执行了,第一步加载我们的mysql的驱动,然后利用DriverManager获取连接

    那么我们将第一步的执行驱动代码注释掉,你猜猜还会执行成功吗??
    根据上面我们说的,答案是肯定的!!!我们进入源码探究一下

    DriverManager类的内部存在一个静态代码块,其中有一个**loadInitialDrivers()**方法:

    在这里插入图片描述

    我们点进去,里面有一行代码:

    //JDK中提供的ServiceLoader来实现服务实现查找功能
    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    

    在这里插入图片描述

    我们点进去这个方法看看:

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
    

    这段代码便是打破双亲委派机制,实现com.mysql.jdbc.Driver驱动类加载的核心,即我们获取线程上下文加载器Thread.currentThread().getContextClassLoader(),然后利用线程上下文类加载器进行mysql驱动的类加载,以达到打破双亲委派机制的作用

    PS:此时如果我们改变线程上下文类加载器,就会使得此时程序出现错误,大家也就明白了其中的流程和原理了

    public class TestClassLoader {
        public static void main(String[] args) throws SQLException, ClassNotFoundException {
            String driverClassName = "com.mysql.jdbc.Driver";
            String url = "jdbc:mysql://localhost:3306/mybatis";
            String username = "root";
            String password = "root";
            Connection connection = null;
            PreparedStatement pstmt = null;
            ResultSet rs = null;
    
            //改变当前线程上下文类加载器为当前类的扩展类加载器
            Thread.currentThread().setContextClassLoader(TestClassLoader.class.getClassLoader().getParent());
            
            //执行驱动"com.mysql.jdbc.Driver"
            //Class.forName("com.mysql.jdbc.Driver");
    
            //获取连接
            connection = DriverManager.getConnection(url,username,password);
        }
    }
    

    执行结果:

    Exception in thread "main" java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/mybatis
    	at java.sql.DriverManager.getConnection(DriverManager.java:689)
    	at java.sql.DriverManager.getConnection(DriverManager.java:247)
    	at it.feng.test.TestClassLoader.main(TestClassLoader.java:25)
    
    展开全文
  • Tomcat打破双亲委派机制

    千次阅读 2021-11-30 13:22:39
    沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载自己实现的 java.lang.String.class public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String ...

    打破双亲委派

    沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载自己实现的 java.lang.String.class

    public class MyClassLoaderTest {
        static class MyClassLoader extends ClassLoader {
            private String classPath;
    
            public MyClassLoader(String classPath) {
                this.classPath = classPath;
            }
    
            private byte[] loadByte(String name) throws Exception {
                name = name.replaceAll("\\.", "/");
                FileInputStream fis = new FileInputStream(classPath + "/" + name
                        + ".class");
                int len = fis.available();
                byte[] data = new byte[len];
                fis.read(data);
                fis.close();
                return data;
    
            }
    
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try {
                    byte[] data = loadByte(name);
                    return defineClass(name, data, 0, data.length);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException();
                }
            }
    
            /**
             * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
             * @param name
             * @param resolve
             * @return
             * @throws ClassNotFoundException
             */
            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) {
                        // 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.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
            }
        }
    
        public static void main(String args[]) throws Exception {
            MyClassLoader classLoader = new MyClassLoader("D:/test");
            //尝试用自己改写类加载机制去加载自己写的java.lang.String.class
            Class clazz = classLoader.loadClass("java.lang.String");
            Object obj = clazz.newInstance();
            Method method= clazz.getDeclaredMethod("sout", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader().getClass().getName());
        }
    }
    
    运行结果:
    java.lang.SecurityException: Prohibited package name: java.lang
    	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
    	at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
    

    Tomcat打破双亲委派机制

    以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?
    Tomcat是个web容器, 那么它要解决什么问题:

    1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
    2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
    3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
    4. web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。

    Tomcat 如果使用默认的双亲委派类加载机制行不行?
    答案是不行的。为什么?
    第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
    第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
    第三个问题和第一个问题一样。
    再看第四个问题,要怎么实现jsp文件的热加载,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
    以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?
    我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:

    1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
    2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。
    3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
    4. web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情, web容器需要支持 jsp 修改后不用重启。

    再看看我们的问题:Tomcat 如果使用默认的双亲委派类加载机制行不行?
    答案是不行的。为什么?
    第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
    第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
    第三个问题和第一个问题一样。
    我们再看第四个问题,我们想我们要怎么实现jsp文件的热加载,jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。

    Tomcat自定义加载器详解

    在这里插入图片描述
    Tomcat的几个主要类加载器
    commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个webapp访问
    catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class不可以为webapp可见
    **shareLoader:**各个webapp共享的类加载器,加载路径中的class可以被所有的webapp访问,但是对于Tomcat容器不可见
    **WebappClassLoader:**各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见,比如加载war包里相关的类,每个war包应用都有自己的WebappClassLoader,实现相互隔离,比如不同war包应用引入了不同的spring版本,这样实现就能加载各自的spring版本

    tomcat 这种类加载机制违背了java 推荐的双亲委派模型了吗?答案是:违背了。
    很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有遵守这个约定,每个webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器,打破了双亲委派机制。

    模拟实现Tomcat的webappClassLoader加载自己war包应用内不同版本类实现相互共存与隔离

    public class MyClassLoaderTest {
        static class MyClassLoader extends ClassLoader {
            private String classPath;
    
            public MyClassLoader(String classPath) {
                this.classPath = classPath;
            }
    
            private byte[] loadByte(String name) throws Exception {
                name = name.replaceAll("\\.", "/");
                FileInputStream fis = new FileInputStream(classPath + "/" + name
                        + ".class");
                int len = fis.available();
                byte[] data = new byte[len];
                fis.read(data);
                fis.close();
                return data;
    
            }
    
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try {
                    byte[] data = loadByte(name);
                    return defineClass(name, data, 0, data.length);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new ClassNotFoundException();
                }
            }
    
            /**
             * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
             * @param name
             * @param resolve
             * @return
             * @throws ClassNotFoundException
             */
            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) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
    
                        //非自定义的类还是走双亲委派加载
                        if (!name.startsWith("com.tuling.jvm")){
                            c = this.getParent().loadClass(name);
                        }else{
                            c = findClass(name);
                        }
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
            }
        }
    
        public static void main(String args[]) throws Exception {
            MyClassLoader classLoader = new MyClassLoader("D:/test");
            Class clazz = classLoader.loadClass("com.jvm.User1");
            Object obj = clazz.newInstance();
            Method method= clazz.getDeclaredMethod("sout", null);
            method.invoke(obj, null);
            System.out.println(clazz.getClassLoader());
            
            System.out.println();
            MyClassLoader classLoader1 = new MyClassLoader("D:/test1");
            Class clazz1 = classLoader1.loadClass("com.jvm.User1");
            Object obj1 = clazz1.newInstance();
            Method method1= clazz1.getDeclaredMethod("sout", null);
            method1.invoke(obj1, null);
            System.out.println(clazz1.getClassLoader());
        }
    }
    
    运行结果:
    =======自己的加载器加载类调用方法=======
    com.jvm.MyClassLoaderTest$MyClassLoader@266474c2
    
    =======另外一个User1版本:自己的加载器加载类调用方法=======
    com.jvm.MyClassLoaderTest$MyClassLoader@66d3c617
    

    注意:同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类加载器也是同一个才能认为他们是同一个。

    模拟实现Tomcat的JasperLoader热加载

    原理:后台启动线程监听jsp文件变化,如果变化了找到该jsp对应的servlet类的加载器引用(gcroot),重新生成新的JasperLoader加载器赋值给引用,然后加载新的jsp对应的servlet类,之前的那个加载器因为没有gcroot引用了,下一次gc的时候会被销毁。

    User类的代码:

    package com.jvm;
    
    public class User {
    
        private int id;
        private String name;
        
        public User() {
        }
    
        public User(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void sout() {
            System.out.println("=======自己的加载器加载类调用方法=======");
        }
    }
    
    展开全文
  • 打破双亲委派机制

    2019-10-29 13:51:17
    打破双亲委派机制代码实现 1,自己写一个类加载器。 2,重写loadclass方法。 3,重写findclass方法。 4,定义Test类。 public class Test { public Test ( ) { System . out . println ( this...

    java类加载器

    1,Bootstrap Classloader:根类加载器,负责加载java的核心类(java.lang.*等),它不是java.lang.ClassLoader的子类,而是由JVM自身实现,c++实现,构造ExtClassLoader和APPClassLoader。
    2,Extension ClassLoader:扩展类加载器,扩展类加载器的加载路径是JDK目录下jre/lib/ext,扩展类的getParent()方法返回null,实际上扩展类加载器的父类加载器是根加载器,只是根加载器并不是Java实现的。
    3,System ClassLoader:系统(应用)类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性或CLASSPATH环境变量所指定的jar包和类路径。程序可以通过getSystemClassLoader()来获取系统类加载器,,如果我们没有实现自定义的类加载器那这玩意就是我们程序中的默认加载器,主要负责加载应用程序的主函数类。

    双亲委派模型的工作过程

    在这里插入图片描述
    当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理会先检查自己是否已经加载过,如果没有再往上。注意这个过程,知道到达Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的。如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。

    双亲委派机制代码

     public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    //              -----??-----
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 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);
                }
            }
            return c;
    }
    

    为什么要设置双亲委派机制

    这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,但是在这种机制下这些系统的类已经被Bootstrap classLoader加载过了,所以并不会再去加载,从一定程度上防止了危险代码的植入。

    如何打破双亲委派模型

    1,自定义类加载器,重写loadClass方法;
    2,使用线程上下文类加载器;

    双亲委派破坏史

    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等。

    以JDBC加载驱动为例:
    在JDBC4.0之后支持SPI方式加载java.sql.Driver的实现类。SPI实现方式为,通过ServiceLoader.load(Driver.class)方法,去各自实现Driver接口的lib的META-INF/services/java.sql.Driver文件里找到实现类的名字,通过Thread.currentThread().getContextClassLoader()类加载器加载实现类并返回实例。

    驱动加载的过程大致如上,那么是在什么地方打破了双亲委派模型呢?

    先看下如果不用Thread.currentThread().getContextClassLoader()加载器加载,整个流程会怎么样。

    从META-INF/services/java.sql.Driver文件得到实现类名字DriverA
    Class.forName(“xx.xx.DriverA”)来加载实现类
    Class.forName()方法默认使用当前类的ClassLoader,JDBC是在DriverManager类里调用Driver的,当前类也就是DriverManager,它的加载器是BootstrapClassLoader。
    用BootstrapClassLoader去加载非rt.jar包里的类xx.xx.DriverA,就会找不到
    要加载xx.xx.DriverA需要用到AppClassLoader或其他自定义ClassLoader
    最终矛盾出现在,要在BootstrapClassLoader加载的类里,调用AppClassLoader去加载实现类

    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,自己写一个类加载器。
    2,重写loadclass方法。
    3,重写findclass方法。
    4,定义Test类。

     public class Test {
      public Test(){
        System.out.println(this.getClass().getClassLoader().toString());
      }
    }
    

    重新定义一个继承ClassLoader的TestClassLoaderN类,它除了重写findClass方法外还重写了loadClass方法,默认的loadClass方法是实现了双亲委派机制的逻辑,即会先让父类加载器加载,当无法加载时才由自己加载。这里为了破坏双亲委派机制必须重写loadClass方法,即这里先尝试交由System类加载器加载,加载失败才会由自己加载。它并没有优先交给父类加载器,这就打破了双亲委派机制。

    public class TestClassLoaderN extends ClassLoader {
    
      private String name;
    
      public TestClassLoaderN(ClassLoader parent, String name) {
        super(parent);
        this.name = name;
      }
    
      @Override
      public String toString() {
        return this.name;
      }
    
      @Override
      public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> clazz = null;
        ClassLoader system = getSystemClassLoader();
        try {
          clazz = system.loadClass(name);
        } catch (Exception e) {
          // ignore
        }
        if (clazz != null)
          return clazz;
        clazz = findClass(name);
        return clazz;
      }
    
      @Override
      public Class<?> findClass(String name) {
    
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
          is = new FileInputStream(new File("d:/Test.class"));
          int c = 0;
          while (-1 != (c = is.read())) {
            baos.write(c);
          }
          data = baos.toByteArray();
        } catch (Exception e) {
          e.printStackTrace();
        } finally {
          try {
            is.close();
            baos.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
        return this.defineClass(name, data, 0, data.length);
      }
    
      public static void main(String[] args) {
        TestClassLoaderN loader = new TestClassLoaderN(
            TestClassLoaderN.class.getClassLoader(), "TestLoaderN");
        Class clazz;
        try {
          clazz = loader.loadClass("test.classloader.Test");
          Object object = clazz.newInstance();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    
    }
    

    如何在父加载器加载的类中,去调用子加载器去加载类?

    1,jdk提供了两种方式,Thread.currentThread().getContextClassLoader()和ClassLoader.getSystemClassLoader()一般都指向AppClassLoader,他们能加载classpath中的类。
    2,SPI则用Thread.currentThread().getContextClassLoader()来加载实现类,实现在核心包里的基础类调用用户代码。

    展开全文
  • JVM - 彻底理解打破双亲委派机制

    千次阅读 多人点赞 2020-06-12 00:11:41
    JVM-白话聊一聊JVM类加载和双亲委派机制源码解析 JVM - 自定义类加载器 何为打破双亲委派 举个例子 有个类 Artisan 我们希望通过自定义加载器 直接从某个路径下读取Artisan.class . 而不是说 通过自定义加载器 委托...
  • Tomcat如何打破双亲委派机制? Tomcat是一个Web框架,并且可以支持部署多个web项目,web项目在Tomcat被抽象的称为Context,即每一个web项目是一个Context。而每个Context是独立的,比如项目A可以引用spring1.0,而...
  • 双亲委派机制时JVM类加载的默认使用的机制,其原理是:当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务时...
  • 为什么说SPI打破双亲委派机制简单介绍ClassLoader的双亲委派机制:java类通过Classloader加载,Classloader之间有继承关系,AppClassLoader继承ExtClassloader继承BootstrapClassloader。在类加载时,子加载器会调用...
  • 由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则在JDK1.0时代就已经存在,面对已经存在的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。...
  • 中对应的类加载器就是AppClassLoader,满足双亲委派机制 4、打破双亲委派机制 例如在Tomact中部署多个项目,每个项目使用的相同但不用版本的组件 自定义类加载器,在加载类时,没有遵循双亲委派机制(先委托父加载器...
  • java默认的类加载器有三个,分别是引导类加载器、扩展类加载器以及应用类加载器。... 那么何为双亲委派机制呢,双亲委派机制就是在进行类加载的时候,如果当前类加载器存在父加载器,那么首相从父类加载器中获取...
  • tomcat在jvm提供的类加载器上进行了扩展,并且打破双亲委托机制CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader则是Tomcat自己定义的类加载器,它们分别加载/common/*、/server/*...
  • tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器...1. 不打破双亲委派一样可以保证不同web应用加载不同位置的内容,这是通过每个web.
  • 文章目录打破双亲委派的原因Demo 打破双亲委派的原因 双亲委派的机制,是AppClassLoader先委托给ExtClassLoader类加载器进行加载,ExtClassLoader加载不了,则交由BootstrapClassLoader进行加载操作; 如果...
  • 类加载机制是什么概论.java文件通过编译以后,读取到jvm的方法区中,然后类加载器的加载、验证(class特定的格式)、准备(分配内存)、解析(将字节码的符号引用改成直接引用)、初始化(对象的初始化)过程几种常见的类...
  • 【JVM笔记】如何打破双亲委派机制

    千次阅读 多人点赞 2020-01-31 23:32:35
    以下主要为重写loadClass()和使用线程上下文类加载器这两种打破双亲委派模型方法的总结,并结合具体实例进行分析。 参考:破坏双亲委派模型 什么地方违反了双亲委派模型 以JDBC为例谈双亲委派模型的破坏 ....
  • Java打破双亲委派机制

    万次阅读 2018-08-10 17:10:00
    沿用双亲委派机制自定义类加载器很简单,只需继承ClassLoader类并重写findClass方法即可。 ①先定义一个待加载的类Test,它很简单,只是在构建函数中输出由哪个类加载器加载。 public class Test { public Test()...
  • Tomcat如何打破双亲委派机制实现隔离Web应用的?

    千次阅读 多人点赞 2021-07-29 01:29:04
    Tomcat通过自定义类加载器WebAppClassLoader打破双亲委托,即重写了JVM的类加载器ClassLoader的findClass方法和loadClass方法,这样做的目的是优先加载Web应用目录下的类。除此之外,你觉得Tomcat的类加载器还需要...
  • 双亲委派机制打破双亲委派示例

    千次阅读 2021-01-01 20:22:03
    怎么打破双亲委派机制:(也就是能向下委派和不委派) 自定义类加载器(不委派) spi机制(向下委派) 打破双亲委派 打破双亲委派的两种方式: 1.通过spi机制,使用ServiceLoader.load去加载 2.通过自定义类...
  • // 不打破双亲委派机制 // 虽然打印结果为false, 但实际上User类只被加载了一次, // 因为myClassLoader1和myClassLoader2 本质都是用共同的父类加载器(即系统类加载器)去加载 System.out.println(userClzVersion1 =...
  • Tomcat 对双亲委派的破坏 首先,我们需要思考一个问题:Tomcat 为什么要打破双亲委派机制? Tomcat 是个 web 容器, 那么它要解决什么问题呢: 一个 web 容器可能需要部署两个应用程序,不同的应用程序可能会依赖同...
  • 文章目录1 打破双亲委派机制 1 打破双亲委派机制 上节自定义的类加载器,实现了加载指定目录的class,但是还是遵循了双亲委派机制 如何打破双亲委派机制 很简单,我们已经知道了双亲委派机制就是在ClassLoader的load...
  • } } 运行结果如下 但是每一次都会使用自定义的类加载器的父类去加载文件,所以我们要打破双亲委派机制,我们找到jdk自带的类ClassLoader下的loadClass方法,将他复制到我们的项目中 protected Class loadClass...
  • 怎么打破双亲委派机制

    万次阅读 多人点赞 2018-08-14 20:47:26
    如何打破双亲委派模型? 自定义类加载器,重写loadClass方法; 使用线程上下文类加载器; 双亲委派模型破坏史 1.第一次破坏 由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang....
  • 双亲委派模型的好处: 在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ...
  • 首先我们说一下什么是双亲委派机制 双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,801
精华内容 1,520
关键字:

打破双亲委派机制

友情链接: 停车场游戏.rar