精华内容
参与话题
问答
  • 双亲委派机制分析

    千次阅读 多人点赞 2018-12-17 00:11:33
    什么是双亲委派双亲委派机制有4种类加载器为: - 自定义(UserClassLoader)->应用/系统(App/SystemClassLoader)->扩展类(ExtClassLoader)->启动(BootstrapClassLoader)类加载器。 加载...
    什么是双亲委派?
    双亲委派机制有4种类加载器为:
    - 自定义(UserClassLoader)->应用/系统(App/SystemClassLoader)->扩展类(ExtClassLoader)->启动(BootstrapClassLoader)类加载器。
    加载过程简述:
    - 当一个类加载某个类.class(需要编译即javac Xx.java>>Xx.class)的时候,不会直接去加载,而是自定义会委托应用/系统,应用/系统会委托扩展,扩展会委托启动类加载器尝试去加载,如果启动类加载器不加载这个,就交给扩展,扩展不行就应用/系统,一层层的下去,然后最终加载到这个.class类。
    加载的量:
    - 不是一次性加载,而是按需动态加载,不然一次性加载内存可能会爆。
     
    双亲委派优点?
    1 安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String
    2 避免全限定命名的类重复加载(使用了findLoadClass()判断当前类是否已加载)
     

    双亲委派的面试题
    题目:可不可以自己写个String类(也是自定义的String为何没加载到?) - 阿里
    不可以。因为在类加载中,会根据双亲委派机制去寻找当前java.lang.String是否已被加载。由于启动类加载器已在启动时候加载了所以不会再次加载,因此使用的String是已在java核心类库加载过的String,而不是新定义的String。
    代码:
    //这里为了测试,将其的包名改成与jdk的rt.jar中的java.lang.String一致。
    package java.lang;   
    public class String {
        static {
            System.out.println(11);
        }
        private String(int i) {
            System.out.println(i);
        }
        //注意:核心类库的String是没有main方法的,因为他找到的核心类库String, 所以报找不到main()方法错误.
        public static void main(java.lang.String[] args) {
            String s = new String(1);
            System.out.println(s);
        }
    }
     
     
    双亲委派的加载过程?
    看完上面的, 我好奇它的每个类加载器有什么区别?代码里是怎么加载的?流程是怎么加载的?
    1 每个类加载器有什么区别?
    BootstrapClassLoader: 
    - 最顶层类加载器,加载java核心类库即%JRE_HOME%\lib下的rt.jarcharsets.jar和class等, 可通过java -Xbootclasspath/a:path(追加)、-Xbootclasspath/p:path(优先加载)、-Xbootclasspath:bootclasspath(替换jdk的rt.jar的加载)指定。
     
    ExtClassLoader:
    - 扩展类加载器,加载%JRE_HOME%\lib\ext的jar和class文件,可用-Djava.ext.dirs=./plugin:$JAVA_HOME/jre/lib/ext (":"是作为分隔符,代表./plugin和ext目录的都被扩展类加载器加载)指定。
     
    App/SystemClassLoader:
    - 应用/系统类加载器,加载当前classpath的所有类。
     
    XxxClassLoader:
    - 用户自定义的类加载器,默认使用双亲委派,委托上级来加载。
     
    代码里是怎么加载的?
    首先找到AppClassLoader、ExtClassLoader,这两个类均在Launcher.java内。从代码分析得如下:
    ---------------------------------------------------------------------------------------
    ...................
    private static String bootClassPath = System.getProperty("sun.boot.class.path");    //35行
    ...................
    String var0 = System.getProperty("java.ext.dirs");    //297行
    ...................
    final String var1 = System.getProperty("java.class.path");  //164行
    ...................
    ---------------------------------------------------------------------------------------
    然后测试以上三个,代码及输出如下:
    System.out.println(System.getProperty("sun.boot.class.path")
    .replaceAll("C:\\\\software\\\\programme\\\\Java\\\\jdk1.8.0\\\\jre", ""));//启动类加载路径
    System.out.println(System.getProperty("java.ext.dirs"));//扩展类加载路径
    System.out.println(System.getProperty("java.class.path"));//应用/系统类加载路径
     
    注:结合1中每个类加载器的区别,就可以知道他们的加载目录究竟在哪里。
     
    知道了他们的加载路径,接下来我们探讨下加载的顺序
    我们先看如下代码:
    ClassLoader classLoader = Demo06.class.getClassLoader();
    使用debug模式调试可见当前类加载器先找到的parent属性(上级类加载器)为Launcher下AppClassLoader,然后AppClassLoader的parent属性(上级类加载器)为ExtClassLoader, ExtClassLoader的parent为null
     
    那么他们的这个parent属性是如何赋值的?详情分析如下代码:
    //首先AppClassLoader、ExtClassLoader都在Launcher下,其结构如下:
    public class Launcher {
        ..................................
        static class AppClassLoader extends URLClassLoader {.......}
        ..................................
        static class ExtClassLoader extends URLClassLoader {.......}
        ..................................
    }
    此上面的类我们可以分析双亲委派是如何设置上级类加载器的,过程如下:
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            //第一步 获取Ext类加载器,Ext类加载器构造方法中初始化了其上级(这里下面代码讲解)
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        try {
            //第二步 获取App类加载器,App类加载器会将var1(即Ext类加载器)传入,然后最终也是传入到其构造方法进行初始化其上级(这里下面代码讲解)
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        //第三步 将获取到的App类加载器设置为当前线程类加载器
        Thread.currentThread().setContextClassLoader(this.loader);
        ..........................
    }
    看完上面的代码,我们来细讲一下第一步、第二步究竟是怎样初始化parent,可先见其继承体系图如下:
     
    注:从继承体系可见AppClassLoader、ExtClassLoader都继承了URLClassLoader、ClassLoader。而ClassLoader里面正定义了这个this.parent
    public abstract class ClassLoader {
        ...................
        private final ClassLoader parent //上级的类加载器
        ...................
        protected ClassLoader() {  //第一种初始化方法(无参),直接传入系统类加载器
            this(checkCreateClassLoader(), getSystemClassLoader());
        }
        ...................
        //第二种有参,传入对应的parent,比如AppClassLoader传入的parent是ExtClassLoader实例,ExtClassLoader传入的是null。
        protected ClassLoader(ClassLoader parent) {  
            this(checkCreateClassLoader(), parent);
        }
        ...................
        private ClassLoader(Void unused, ClassLoader parent) {
            this.parent = parent //然后无论是哪种,最后都会传入到这个构造方法,然后赋值给this.parent
            if (ParallelLoaders.isRegistered(this.getClass())) {
                parallelLockMap = new ConcurrentHashMap<>();
                package2certs = new ConcurrentHashMap<>();
                domains Collections.synchronizedSet(new HashSet<ProtectionDomain>());
                assertionLock = new Object();
            } else {
                // no finer-grained lock; lock on the classloader instance
                parallelLockMap = null;
                package2certs = new Hashtable<>();
                domains = new HashSet<>();
                assertionLock = this;
            }
        }
    }
    "如何设置上级类加载器"分析总结:在launcher中app、ext类加载器已经初始化对应的构造方法,然后其对应的构造方法都会调用super(parent)然后分别将ext、null传入最终传到ClassLoader的构造方法中的this.parent = parent。其传递给过程取ExtClassLoader作为示例如下:
    //1 Launcher下的ExtClassLoader
    public ExtClassLoader(File[] var1) throws IOException {
        super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
        SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }
    //2 URLClassLoader下
    public URLClassLoader(URL[] urls, ClassLoader parent,
        URLStreamHandlerFactory factory) {
        super(parent);
      ...............
    }
    //3 SecureClassLoader下
    protected SecureClassLoader(ClassLoader parent) {
        super(parent);
        ........................
    }
    //4 ClassLoader下
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    private ClassLoader(Void unused, ClassLoader parent) {
            this.parent = parent //然后无论是哪种,最后都会传入到这个构造方法,然后赋值给this.parent,然后设置好了上级类加载器
    }
     
     
    上面我们分析完了每个上级类加载器是怎么拿到的,接下来我们探讨下类加载器间是如何进行双亲委托的?
     
    由图分析得:双亲委托机制采用的是"向上委托,向下查找",其步骤如下:
    第一步(向上委托) 当前类加载对.class进行加载,先会找到上级类加载器AppClassLoader,然后去缓存查是否有已加载的类,如果没有则去上级ExtClassLoader缓存查找是否有已加载的类,如果没有则再往上Bootstrap缓存找是否有已加载的类,如果没有就会进入第二步,反之上面任何一步缓存查找有的话,都会直接返回缓存里加载了的.class,而不会继续往上级找
     
    第二步(向下查找) 第一步缓存找不到时就会进入第二步。此时已经到了Bootstrap,Bootstrap会先到其对应的加载目录(sun.mic.boot.class路径)去看看当前有没这个类加载,如果有就加载返回返回;没有则往下级ExtClassLoader的对应加载目录(java.ext.dirs路径)找,有就加载返回,无就往下走;走到AppClassLoader然后去其对应加载目录(java.class.path路径)加载,有就加载,没有则让子类找,如果还失败就抛异常,然后调用当前ClassLoader.findClass()方法加载
    :findClass是子类实现的,所以是用来自定义类加载器的。
     
    其中这个加载过程涉及到了几个重要方法:loadClass、findLoadClass、findClass、defindClass。
     
    //1 loadClass分析步骤(ClassLoader.java中):
    //- finadLoadClass检查当前class是否被加载过;
    //- 执行父加载器loadClass,如果没加载到会一直loadClass到Bootstrap ClassLoader,此时parent=null;
    //-   如果向上委托没加载成功就使用findClass向下查找;
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) { //loadClass加了synchronized,是同步的
            // 检查当前全限定类名(截图如上)下的包是否已被加载过,最终调用本地方法中(native final Class<?> findLoadedClass0(String name))
            Class<?> c = findLoadedClass(name);
            //如果当前没被加载过,就重新加载一次
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //!!重点!!看当前有没上级类加载器,一般是AppClassLoader、ExtClassLoader,
                    //而且这个loadClass会调用上级的loadClass,一直调用到parent=null的loadClass()
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {//!!重点!!如果当前parent为null,那应该就是Bootstrap classLoader(因为这个是底层是C++,所以不直接调)
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {}
                
                if (c == null) {
                    long t1 = System.nanoTime();
                    //上面两个重点的父类加载器没找到,则调用findClass
                    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) {//为true调用resolveClass
                resolveClass(c);
            }
            return c;
        }
    }
     
    上面解析了系统自带的几个类加载器如Ext、bootstrap是怎么加载的,
    但是为什么还需要自定义类加载器??
    1 从非标准来源加载代码:由于系统提供的类加载器均加载的是指定目录,所以当我们需要加载非系统指定目录如C:/xx/xxxx.class时需要自定义类加载器、数据库、云端等
    2 加密:将编译后的代码加密,然后用自定义类加载器去先解密,然后再加载。
    自定义类加载器过程如下(自定义默认parent为AppClassLoader):
    1 继承ClassLoader抽象类
    2 重写findClass() 
    3 重写的findClass()中调用defineClass()
    其代码实现过程如下:
    • 第一步:编写Test测试类Test.java, 然后使用javac Test.java编译成Test.class并拷贝到C:\test目录下
    package com.lisam.test;  //放在其他非规定位置时,加这个会报找不到
    public class Test {
        public void test(){
            System.out.println("test成功");
        }
    }
    • 第二步:编写自定义类加载器
    // 1 继承ClassLoader抽象类
    class MyClassLoader extends ClassLoader {
        private String path;
        MyClassLoader(String path) {
            this.path = path;
        }
     
        // 2 重写findClass()
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            File file = new File(this.path);
            try {
                byte[] bytes = getClassBytes(file);
                //3 defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
                return this.defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return super.findClass(name);
        }
        //这里反正当前文件的比特流即可
        private byte[] getClassBytes(File file) throws IOException {
            //使用字节流获取.class
            FileInputStream fis = new FileInputStream(file);
            FileChannel fc = fis.getChannel();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            WritableByteChannel wbc = Channels.newChannel(baos);
            ByteBuffer by = ByteBuffer.allocate(1024);
            while (true) {
                int     i = fc.read(by);
                if (i == 0 || i == -1)
                    break;
                by.flip();
                wbc.write(by);
                by.clear();
             }
            fis.close();
            return baos.toByteArray();
        }
    }
    • 第三步 使用并输出结果
    @Test
    public void test01() throws Exception {
        MyClassLoader mcl = new MyClassLoader("C:\\test\\Test.class");
        Class<?> clazz = Class.forName("Test", true, mcl);
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("test", null);
        method.invoke(obj, null);
        System.out.println(obj);
        System.out.println(obj.getClass().getClassLoader());//打印出我们的自定义类加载器
    }
     
    展开全文
  • 双亲委派模型有两个好处: 向上委托给父类加载,父类加载不了再自己加载 避免重复加载,防止Java核心api被篡改 加载器自上而下分别为,启动类加载器(Bootstrap ClassLoader), 拓展类加载器(Extension ClassLoader)...

    双亲委派模型有两个好处:

    1. 向上委托给父类加载,父类加载不了再自己加载
    2. 避免重复加载,防止Java核心api被篡改

    加载器自上而下分别为,启动类加载器(Bootstrap ClassLoader), 拓展类加载器(Extension ClassLoader), 系统类加载器(Application ClassLoader) , 自定义类加载器(Custom ClassLoader)

    在这里插入图片描述

    双亲委派模式是Java1.2之后引入的,其工作原理是,如果其中一个类加载器收到了类加载的请求,它并不会自己去加载而是会将该请求委托给父类的加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,如此递归,请求最终到达顶层的启动类加载器。如果父类能加载,则直接返回,如果父类加载不了则交由子类加载,这就是双亲委派模式。

    展开全文
  • 通俗易懂的双亲委派机制

    万次阅读 多人点赞 2018-09-27 23:24:08
    在介绍双亲委派机制的时候,不得不提ClassLoader。说ClassLoader之前,我们得先了解下Java的基本知识。 Java是运行在Java的虚拟机(JVM)中的,但是它是怎么就运行在JVM中了呢?我们在IDE中编写的Java源代码被编译器...

    你得先知道

    在介绍双亲委派机制的时候,不得不提ClassLoader。说ClassLoader之前,我们得先了解下Java的基本知识。  
      Java是运行在Java的虚拟机(JVM)中的,但是它是怎么就运行在JVM中了呢?我们在IDE中编写的Java源代码被编译器编译成.class的字节码文件。然后由我们得ClassLoader负责将这些class问价加载到JVM中去执行。  
      JVM中提供了三层的ClassLoader:

    • Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。

    • ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。

    • AppClassLoader:主要负责加载应用程序的主函数类

      那如果有一个Hello.class文件是如何被加载到JVM中的呢?

    双亲委派机制

    我打开了我的AndroidStudio,搜索了下“ClassLoader”,然后打开“java.lang”包下的ClassLoader类。然后将代码翻到loadClass方法:

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

    其实这段代码已经很好的解释了双亲委派机制,为了大家更容易理解,我做了一张图来描述一下上面一段代码到底是怎么做的:  

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

    为什么要设计这种机制

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

    送给大家一些超清PDF书籍资料:https://mp.weixin.qq.com/s/fF6kWpNMFMMZsUP2FaXR8A

    ➡️电子书原文⬅️

    如果对你有一点点的帮助,欢迎关注??获取更多知识哦~

    微信搜索:IT烂笔头  关注回复【职场】获得作者好友位

     

    展开全文
  • 【JVM】浅谈双亲委派和破坏双亲委派

    千次阅读 多人点赞 2018-08-09 17:22:53
    【JVM】浅谈双亲委派和破坏双亲委派 转载自 【JVM】浅谈双亲委派和破坏双亲委派 目录 一、前言二、双亲委派 2.1、为什么需要双亲委派 2.2、双亲委派的实现三、破坏双亲委派 3.1、为什么需要破坏双亲委派? 3.2、...

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

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

    目录

    一、前言
    二、双亲委派
    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.DifferentClassLoaderTest
    false

    如果在通过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里无法获取,负责加载/lib下的类。
    2. 扩展类加载器(Extension ClassLoader): Java实现,可以在java里获取,负责加载/lib/ext下的类。
    3. 系统类加载器/应用程序类加载器(Application ClassLoader):是与我们接触对多的类加载器,我们写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。

    附上三者的关系:

    双亲委派图

     

    2.2、双亲委派的实现

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

    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);
                        // 前面提到,bootstrap classloader的类加载器为null,通过find方法来获得
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                    }
    
                    if (c == null) {
                        // 如果还是没有获得该类,调用findClass找到类
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // jvm统计
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                // 连接类
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }

     

    三、破坏双亲委派

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

    因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。

    3.2、破坏双亲委派的实现

    我们结合Driver来看一下在spi(Service Provider Inteface)中如何实现破坏双亲委派。

    先从DriverManager开始看,平时我们通过DriverManager来获取数据库的Connection:

    String url = "jdbc:mysql://localhost:3306/testdb";
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "root"); 

    在调用DriverManager的时候,会先初始化类,调用其中的静态块:

    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
    
    private static void loadInitialDrivers() {
        ...
            // 加载Driver的实现类
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
    
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                    Iterator<Driver> driversIterator = loadedDrivers.iterator();
                    try{
                        while(driversIterator.hasNext()) {
                            driversIterator.next();
                        }
                    } catch(Throwable t) {
                    }
                    return null;
                }
            });
        ...
    }

    为了节约空间,笔者省略了一部分的代码,重点来看一下ServiceLoader.load(Driver.class)

    public static <S> ServiceLoader<S> load(Class<S> service) {
        // 获取当前线程中的上下文类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    可以看到,load方法调用获取了当前线程中的上下文类加载器,那么上下文类加载器放的是什么加载器呢?

    public Launcher() {
        ...
        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        Thread.currentThread().setContextClassLoader(this.loader);
        ...
    }

    sun.misc.Launcher中,我们找到了答案,在Launcher初始化的时候,会获取AppClassLoader,然后将其设置为上下文类加载器,而这个AppClassLoader,就是之前上文提到的系统类加载器Application ClassLoader,所以上下文类加载器默认情况下就是系统加载器

    继续来看下ServiceLoader.load(service, cl)

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader){
        return new ServiceLoader<>(service, loader);
    }
    
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        // ClassLoader.getSystemClassLoader()返回的也是系统类加载器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }
    
    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

    上面这段就不解释了,比较简单,然后就是看LazyIterator迭代器:

    private class LazyIterator implements Iterator<S>{
        // ServiceLoader的iterator()方法最后调用的是这个迭代器里的next
        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
        
        private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            // 根据名字来加载类
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }
        
        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
        
        
        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // 在classpath下查找META-INF/services/java.sql.Driver名字的文件夹
                    // private static final String PREFIX = "META-INF/services/";
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
    
    }

    好了,这里基本就差不多完成整个流程了,一起走一遍:

    spi加载过程

     

    四、总结

    Driver剩余的加载过程就省略了,有兴趣的园友可以继续深入了解一下,不得不说,jvm博大精深,看起来容易,真正到了用起来才发现各种问题,也只有实践才能加深理解,最后谢谢各位园友观看,如果有描述不对的地方欢迎指正,与大家共同进步!

    展开全文
  • 双亲委派机制  双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器。每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试...
  • 概述 Java SPI机制指的是java来定义接口,然后由不同的...下面就来探索程序中如何去获取这个具体实现以及类加载器的双亲委派机制在其中的作用。 Java SPI示例 1、实现服务 假设java.lang.Runnable是一个服务,需要各...
  • 在开始阅读之前请先思考以下两个问题,并希望您能再接下来的文章中找到答案 1. 如果我自己实现了一个新的java.lang.String类,并通过UrlClassLoader加载使用该类,能否覆盖JDK中的 java.lang....一、双亲委派 ...
  • 这里大量参考了如下博客 ...ClassLoader是一个抽象类,他有很多个实现 BootstrapClassLoader是最顶层的classloader 负责加载jvm需要的类 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的j.....
  • 因自己的工作中用到的技术很老,接触不到分布式相关的东西。出于对技术的兴趣,大概几个月前加入一个dubbo源码群,见过太多大神,自己感触很深,或者说受到了一定程度的刺激,特转该文并且整理在word文档作为学习...
  • 学习目标 双亲委派模型的⼯作机制。 类加载器的分类及各⾃的职责。 双亲委派模型的好处。 打破双亲委派模型的三种场景。 线程上下⽂类加载器在是如何实现 SPI 的。 ...
  • 我们经常会说双亲委派,这和类加载器有什么关系呢?我们在日常工作中,会设计到双亲委派吗?
  • 双亲委派模式的优点

    千次阅读 2019-06-06 16:39:17
    一道面试题 能不能自己写个类叫java.lang.System? 答案:通常不可以,但可以采取另类方法达到这个需求。 解释:为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就...
  • 一文搞懂双亲委派模型

    千次阅读 2019-06-28 14:42:07
    类加载器 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取此类的二进制字节流”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类...
  • 双亲委派

    2019-07-02 12:31:42
    父类加载器和子类加载器不一定是继承关系; 除了BootstrapLoader,每个类加载器都有一个父类加载器 类加载器之间的父子关系何时建立?在自定义加载器的构造方法编码时需要指定一个父类加载器,如果没有指定就将...
  • 双亲委派机制

    2019-09-04 20:26:11
    前言 我们知道,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在...为了解决JVM的唯一性问题,就引入了双亲委派机制 双亲委派机制 public Class<?> loadClass(String name) throws ClassNo...
  • 这一篇要介绍的就是自定义类加载器来打破双亲委派机制. 思路就是加载类的时候不走parent属性来找,直接从指定的classpath中找 package com.bonc.jvm; import java.io.FileInputStream; import java.lang.reflect....
  • JVM - 彻底理解打破双亲委派机制

    千次阅读 2020-06-12 00:11:41
    JVM-白话聊一聊JVM类加载和双亲委派机制源码解析 JVM - 自定义类加载器 何为打破双亲委派 举个例子 有个类 Artisan 我们希望通过自定义加载器 直接从某个路径下读取Artisan.class . 而不是说 通过自定义加载器 委托...
  • Java中的双亲委派机制以及如何打破 什么是双亲委派机制 当一个类收到了类的加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一层的类加载器都是如此,因此所有的加载请求都应该传送到...
  • Java打破双亲委派机制

    千次阅读 2018-08-10 17:10:00
    沿用双亲委派机制自定义类加载器很简单,只需继承ClassLoader类并重写findClass方法即可。 ①先定义一个待加载的类Test,它很简单,只是在构建函数中输出由哪个类加载器加载。 public class Test { public Test()...
  • 深入理解双亲委派机制及作用

    千次阅读 2020-04-03 16:34:38
    java双亲委派机制及作用 一、什么是双亲委派机制 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。 二、...

空空如也

1 2 3 4 5 ... 20
收藏数 23,006
精华内容 9,202
关键字:

双亲委派