精华内容
下载资源
问答
  • 1.什么是双亲委托模式?2.如何打破双亲委托模式?3.打破类加载模式有什么应用和意义?黄桂钊:90,谈谈类加载过程的双亲委托机制?​zhuanlan.zhihu.comJava 中的双亲委派的“双”怎么理解 ?​...

    1.什么是双亲委托模式?

    2.如何打破双亲委托模式?

    3.打破类加载模式有什么应用和意义?

    黄桂钊:90,谈谈类加载过程的双亲委托机制?zhuanlan.zhihu.com
    Java 中的双亲委派的“双”怎么理解 ?www.zhihu.com
    1680719ce83f8c34a9da7a8bfa94124b.png
    Adelaide:classLoader双亲委托与类加载隔离zhuanlan.zhihu.com
    双亲委派模型有什么优点?www.zhihu.com
    【面试题】jvm中如何打破双亲委托机制?www.zhihu.com
    Java 类加载器(ClassLoader)的实际使用场景有哪些?www.zhihu.com
    cdb5f4e520930d1e4ab49890f73bb8c2.png
    天风:java new一个对象的过程中发生了什么zhuanlan.zhihu.com
    动力节点:阿里内推面试题:能不能自定义一个类叫java.lang.Systemzhuanlan.zhihu.com
    b4c7e849e5067484c6b0146e46b22f36.png
    为什么说java spi破坏双亲委派模型?www.zhihu.com
    展开全文
  • 双亲委托机制

    2021-04-04 14:50:41
    文章目录1 双亲委托机制介绍2 破坏双亲委托机制 1 双亲委托机制介绍 类加载器最重要的机制——双亲委托机制,有时候也称为父委托机制。当一个类加载器被调用了loadClass之后,它并不会直接将其加载,而是先交给当前...

    可先阅读:JVM类加载器

    1 双亲委托机制介绍

    类加载器最重要的机制——双亲委托机制,有时候也称为父委托机制。当一个类加载器被调用了loadClass之后,它并不会直接将其加载,而是先交给当前类加载器的父加载器尝试加载直到最顶层的父加载器,然后再依次向下进行加载

    在这里插入图片描述

    loadClass方法源码:

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    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 {
                	// 如果当前类存在父类加载器,则调用父类加载器的loadClass(name,false)方法对其进行加载。
                    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();
                    // 如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的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();
                }
            }
            // 由于loadClass指定了resolve为false,所以不会进行连接阶段的继续执行,这也就解释了为什么通过类加载器加载类并不会导致类的初始化。
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    
    1. 从当前类加载器的已加载类缓存中根据类的全路径名查询是否存在该类,如果存在则直接返回。
    2. 如果当前类存在父类加载器,则调用父类加载器的loadClass(name,false)方法对其进行加载。
    3. 如果当前类加载器不存在父类加载器,则直接调用根类加载器对该类进行加载。
    4. 如果当前类的所有父类加载器都没有成功加载class,则尝试调用当前类加载器的findClass方法对其进行加载,该方法就是我们自定义加载器需要重写的方法。
    5. 最后如果类被成功加载,则做一些性能数据的统计。
    6. 由于loadClass指定了resolve为false,所以不会进行连接阶段的继续执行,这也就解释了为什么通过类加载器加载类并不会导致类的初始化。

    可见先回调用父加载器,如果父加载器没有加载,最后通过自己加载类

    为了更清楚的认识一下:之前的测试自定义类加载器的时候,需要保证测试类的classpath下没有要加载的类:自定义类加载器

    现在在测试类的classpath下,也定义一个HelloWorld类

    在这里插入图片描述

    运行测试类发现输出类加载器已经不是自定义的加载器了

    类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$AppClassLoader@18b4aac2
    Hello World Class is Initialized.
    Hello World
    

    因为自定义的类加载器默认的父加载器是AppClassLoader,而AppClassLoader就是主要就是加载classpath下的类,而当前classpath下存在这么一个类,所以优先被父加载器加载了,而不会被我们自定义的类加载器加载。

    当前classpath下不存在这么一个类,还是优先被父加载器加载,但是父加载器此时无法加载到这个类,就会被自定义加载器去加载。

    如果你理解了,想想如何即使classpath存在HelloWorld这个类,使测试代码

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        MyClassLoader classLoader = new MyClassLoader();
        // 这里还是使用MyClassLoader 加载
        Class<?> aClass = classLoader.loadClass("study.wyy.thread.join.HelloWorld");
        System.out.println("类加载器:" + aClass.getClassLoader());
        Object helloWorld  = aClass.newInstance();
        Method welcomeMethod = aClass.getMethod("welcome");
        String result = (String) welcomeMethod.invoke(helloWorld);
    
        System.out.println(result);
    }
    

    Class<?> aClass = classLoader.loadClass("study.wyy.thread.join.HelloWorld");还是使用MyClassLoader 加载呢?

    1. 绕过系统类加载器:如果设置了MyClassLoader的父加载器是ExtClassLoader,而ExtClassLoader是不会加载classpath下的类,所以父加载器又无法加载,那么又会交给我们自定义的MyClassLoader加载
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 获取ExtClassLoader
        ClassLoader parent = MyClassLoaderTest.class.getClassLoader().getParent();
        // 设置父加载器是ExtClassLoader
         String classDir = System.getProperty("user.home") + "/MyClassLoader";
        MyClassLoader classLoader = new MyClassLoader(classDir,parent);
    
        Class<?> aClass = classLoader.loadClass("study.wyy.thread.jvm.HelloWorld");
        System.out.println("类加载器:" + aClass.getClassLoader());
        Object helloWorld  = aClass.newInstance();
        Method welcomeMethod = aClass.getMethod("welcome");
        String result = (String) welcomeMethod.invoke(helloWorld);
    
        System.out.println(result);
    }
    

    输出:

    类加载器:My ClassLoader
    Hello World Class is Initialized.
    Hello World
    
    1. 直接设置父加载器是null:如果当前类加载器不存在父类加载器,则直接调用根类加载器对该类进行加载。根类加载器也不会加载classpath下的类
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        String classDir = System.getProperty("user.home") + "/MyClassLoader";
        MyClassLoader classLoader = new MyClassLoader(classDir,null);
        Class<?> aClass = classLoader.loadClass("study.wyy.thread.jvm.HelloWorld");
        System.out.println("类加载器:" + aClass.getClassLoader());
        Object helloWorld  = aClass.newInstance();
        Method welcomeMethod = aClass.getMethod("welcome");
        String result = (String) welcomeMethod.invoke(helloWorld);
    
        System.out.println(result);
    }
    
    类加载器:My ClassLoader
    Hello World Class is Initialized.
    Hello World
    

    2 破坏双亲委托机制

    类加载器的父委托机制的逻辑主要是由loadClass来控制的,有些时候我们需要打破这种双亲委托的机制,比如HelloWorld这个类就是不希望通过系统类加载器对其进行加载。虽然在上面给出了两种解决方案,但是采取的都是绕过ApplicationClassLoader的方式去实现的,并没有避免一层一层的委托,那么有没有办法可以绕过这种双亲委托的模型呢?

    JDK提供的双亲委托机制并非一个强制性的模型,程序开发人员是可以对其进行灵活发挥破坏这种委托机制的,比如我们想要在程序运行时进行某个模块功能的升级,甚至是在不停止服务的前提下增加新的功能,这就是我们常说的热部署。热部署首先要卸载掉加载该模块所有Class的类加载器,卸载类加载器会导致所有类的卸载,很显然我们无法对JVM三大内置加载器进行卸载,我们只有通过控制自定义类加载器才能做到这一点。

    既然双亲委派逻辑就是在loadClass方法实现的,想打破那就重写该方法:
    下面的代码大部分和之前的自定义的类加载器一样,只是重写了loadClass方法。

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    /**
     * @author wyaoyao
     * @date 2021/4/4 16:27
     * 打破双亲委派机制演示:重写loadClass
     *
     */
    public class BrokerDelegateClassLoader extends ClassLoader{
    
    
        /**
         * 定义默认加载class文件的目录
         */
        private final static Path DEFAULT_CLASS_DIR = Paths.get(System.getProperty("user.home"), "MyClassLoader");
    
        private final Path classDir;
    
        public BrokerDelegateClassLoader() {
            super();
            this.classDir = DEFAULT_CLASS_DIR;
        }
    
        public BrokerDelegateClassLoader(String classDir) {
            super();
            this.classDir = Paths.get(classDir);
        }
    
        /**
         * 指定class路径的同时,指定父类加载器
         *
         * @param classDir
         * @param parent
         */
        public BrokerDelegateClassLoader(String classDir, ClassLoader parent) {
            super(parent);
            this.classDir = Paths.get(classDir);
        }
    
        /**
         * 指定父类加载器
         *
         * @param parent
         */
        public BrokerDelegateClassLoader(ClassLoader parent) {
            super(parent);
            this.classDir = DEFAULT_CLASS_DIR;
        }
    
        /**
         * 重写findClass方法
         *
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 将class文件读入内存
            byte[] classByte = this.readClassByte(name);
            // 如果数据为null,或者没有读到任何信息,则抛出ClassNotFoundException异常
            if (null == classByte || classByte.length == 0) {
                throw new ClassNotFoundException("Can not load the class " + name);
            }
            // 调用defineClass方法定义class
            return this.defineClass(name, classByte, 0, classByte.length);
        }
    
        /**
         * 读取clas文件
         *
         * @param name
         * @return
         */
        private byte[] readClassByte(String name) throws ClassNotFoundException {
            // 将包名分隔符转为文件路径分隔符
            String classPath = name.replace(".", "/");
            Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));
            if (!classFullPath.toFile().exists()) {
                throw new ClassNotFoundException("The class" + name + "not find");
            }
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try {
                Files.copy(classFullPath, baos);
                return baos.toByteArray();
            } catch (IOException e) {
                throw new ClassNotFoundException("load the class " + name + " occur error.", e);
            }
        }
    
        @Override
        protected Class<?> loadClass(String name, boolean resolve)
                throws ClassNotFoundException {
            // 根据类的全路径名称进行加锁,确保每一个类在多线程的情况下只被加载一次。
            synchronized (getClassLoadingLock(name)) {
                // 到已加载类的缓存中查看该类是否已经被加载,如果已加载则直接返回。
                Class<?> clazz = findLoadedClass(name);
                // 若缓存中没有被加载的类,则需要对其进行首次加载
                if (null == clazz) {
                    // 则需要对其进行首次加载,如果类的全路径以java和javax开头,则直接委托给系统类加载器对其进行加载。
                    if (name.startsWith("java.") || name.startsWith("javax")) {
                        try {
                            clazz = getSystemClassLoader().loadClass(name);
                        } catch (Exception e) {
                            //ignore
                        }
                    } else {
                        try {
                            // 交给自定义类加载器完成对类的加载 (先交给自己,而不是交给父加载器)
                            clazz = this.findClass(name);
                        } catch (ClassNotFoundException e) {
                            //ignore
                        }
                        // 若自定义类加载仍旧没有完成对类的加载,则委托给其父类加载器进行加载或者系统类加载器进行加载。
                        if (null == clazz) {
                            // 获取父加载器,使用父加载器加载
                            ClassLoader parent = getParent();
                            if (parent != null) {
                                clazz = parent.loadClass(name);
                            } else {
                                // 如果父加载器是null,就使用系统加载器加载
                                clazz = getSystemClassLoader().loadClass(name);
                            }
    
                        }
                    }
                }
    
                if (clazz == null) {
                    throw new ClassNotFoundException("The class " + name + " not found.");
                }
    
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }
        }
    }
    
    

    测试:

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    
        BrokerDelegateClassLoader classLoader = new BrokerDelegateClassLoader();
    
        Class<?> aClass = classLoader.loadClass("study.wyy.thread.jvm.HelloWorld");
        System.out.println("类加载器:" + aClass.getClassLoader());
        Object helloWorld  = aClass.newInstance();
        Method welcomeMethod = aClass.getMethod("welcome");
        String result = (String) welcomeMethod.invoke(helloWorld);
    
        System.out.println(result);
    }
    
    类加载器:study.wyy.thread.jvm.BrokerDelegateClassLoader@6e0be858
    Hello World Class is Initialized.
    Hello World
    

    此时在测试的时候,就不要再classPath下删除Helloworld这个类了

    展开全文
  • 虽然前面把class文件的产生到加载使用流程说了一遍,但是还是想具体看看classLoader的双亲委托具体是如何运行的,有什么利弊。还有想看看不同类加载器的不同命名空间带来那些好处和实际有那些应用?并且想对...

    虽然前面把class文件的产生到加载使用流程说了一遍,但是还是想具体看看classLoader的双亲委托具体是如何运行的,有什么利弊。

    还有想看看不同类加载器的不同命名空间带来那些好处和实际有那些应用?并且想对ClassLoader加载类这个过程进行更加底层的了解,通过阅读源代码和自定义类加载器方式实践。

    双亲委托机制?

    还是先看看JVM中的类加载器层次结构如下:

    Bootstrap classLoader

    /\

    /||\

    Extenssion ClassLoader

    /\

    /||\

    Application ClassLoader

    /| |\

    User ClassLoader User ClassLoader(自定义类加载器)

    我们应用程序中的所有类,都是这几种类加载器加载的。当JVM从某个二进制字节流中读取类的时候,具体这几个类加载器是如何工作的呢?它们之间除了看上去的父子层次关系,在具体的加载类的时候,又有怎样的关系?

    什么是双亲委托,原理机制是什么?

    JVM中的classLoader在搜索某个类的时候,是使用双亲委托模型机制工作的。

    该模型的前提条件就是:除了JVM自带的BootstrapClassLoader引导类加载器外,其他的类加载器都必须属于自己的父加载器(不是以继承方式实现父子关系,而是使用组合包含关系)。所以,这种父子层级之间的关系,就将某个类加载过程给规定好了。

    我们也可以从java.lang.ClassLoader.class中看到这种组合模式形成的类加载器父子层级关系:

    public abstract class ClassLoader {

    private static native void registerNatives();

    static {

    registerNatives();

    }

    // The parent class loader for delegation

    // Note: VM hardcoded the offset of this field, thus all new fields

    // must be added *after* it.

    private final ClassLoader parent; //父类加载器s

    ...

    }

    所以,基于这种组合模式形成的类加载器父子层级关系背景下,双亲委托其工作过程:(假设C加载器是最顶层的Bootstrap引导类加载器)

    如果一个类加载器(classLoader)A收到了加载类的请求,那么A首先不会自己去尝试加载这个类,而是把这个”请求”委托给A的父加载器B去完成,调用getParent()方法可以得到自己的父加载(若方法返回null表示该加载器为引导类加载器)。加载器B也会把该请求委派给自己的父加载器C。在加载该类的每一层都会进行如此父类处理委托。

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

    eg:

    请求加载类tclass,根据当先系统上下文中得到当前的类加载器A,判断A是否有父加载器,若是有,则将该请求发送给父加载器B,在请求发送给C。

    若是C无法加载该tclass(Bootstrap加载/lib下的类),就让B加载,若是B成功加载则完成该类tclass的加载,成功生成对应的java.lang.Class实例,A不需要在进行加载。若是B也无法加载,则最后交给A加载。

    若是A加载成功,返回Class实例,若是加载失败,抛出class相关的异常,程序结束。

    同时,可以查看JDK的源代码中是如何表达该过程的:

    java.lang.ClassLoader中对于该方法的解释是这样的:(英文翻译有点渣哈,╮(╯▽╰)╭…)

    若是要加载name指定的二进制字节码流文件,类加载器默认会按照以下步骤来搜索类:

    a. 调用findLoadedClass方法来检查该类是否已经被类加载器加载了。

    b. 如果当前上下文的类加载器的父加载器不为空,则调用父加载器的loadClass方法来查找类;若是没有父加载器,则使用JVM内建的Bootstrap加载器来查找类。

    c.最后,才是调用findClass方法去查找该类。

    protected Class> loadClass(String name, boolean resolve)

    throws ClassNotFoundException

    {

    synchronized (getClassLoadingLock(name)) {

    //A. First, check if the class has already been loaded

    Class c = findLoadedClass(name);

    if (c == null) {

    long t0 = System.nanoTime();

    try {

    if (parent != null) { //B.有父加载器,则委托父加载器调用loadClass方法查找加载类

    c = parent.loadClass(name, false);

    } else {

    c = findBootstrapClassOrNull(name); //B.无父加载器,则委托JVM内建启动类加载器加载该类

    }

    } 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); //C.若是所有父加载器都无法加载该类字节码,则调用findClass方法去查找类。

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

    }

    }

    我们可以看到第三步骤C中,当所有父加载器都无法加载该类的时候,需要调用findClass方法。

    而源代码中该类仅仅只有一个protected访问权限的方法声明,根本没有实现内容,侧面就说明了,这个方法是提供给开发人员自定义拓展用的。

    用于定义自定义的ClassLoader,覆盖该findClass方法。那么当在程序运行中,在双亲委托无法加载该类的最后一步,就是需要使用我们自定义的类加载器调用自定义的findClass来覆盖如何实现加载该类的细节。

    protected Class> findClass(String name) throws ClassNotFoundException {

    throw new ClassNotFoundException(name);

    }

    具体如何自定义类加载器,在后面会说啦。。下图是双亲委托模型的流程图:

    format,png

    load_class.png

    为什么要使用这种双亲委托模型?

    使用双亲委托模型好处有以下几点:

    可以避免java类的重复加载,当父类加载器已经加载了,那么子加载器就没有必要重新加载一遍。而java类的双亲委托模型的搜索过程总比类加载器再加载一次耗时少,节省资源把。

    可以避免用户自定义同路径类对于J2SE平台自己定义的核心API的破坏。例如java.lang.Object类,它存放在rt.jar包中,无论哪一个类加载器需要加载这个类,最终都是委派给处于最顶端的启动类加载器(Bootstrap ClassLoader)进行加载,因此,Object类在所有程序各种类加载器环境中(也相当于类加载器命名空间中)都是同一个类(没办法,因为引导类加载器是JVM启动自带首先启动的,是第一次加载所有基础类库的类加载器)。

    相反,若是不使用双亲委托机制,由各个类加载器去自行加载的话,如果用户自定义了一个java.lang.Object类,放在Classpath中,那么系统将会出现多个不同的Object类,导致java类型体系中最基础的行为也就无法保证正常运行。双亲委托模型保证了java程序运行的稳定。

    基于JVM标识每个类的唯一性需要与类加载器一同来判断,那么,通过我们自定义的类加载器加载的类,就能很灵活和方便的与其他甚至同名的类区分开来,进行隔离使用。大大增强了我们对类的使用。

    这种委托机制的利弊,如何理解双亲委派模型的被破化?

    JVM规范中,双亲委托模型并不是一个强制性的约束,而是java设计者推荐给开发者的类加载实现方式。在java的世界中大部分的类加载器都遵循这个模型。但到目前为止,该模型主要出现以下几次大规模”被破坏”的情况:(参考《深入理解JVM虚拟机》)

    a. 双亲委托模型是在JDK1.2之后出现的。在此之前类加载器和抽象类java.lang.ClassLoader就已经存在了。所以为了向前兼容,JDK1.2之后的java.lang.ClassLoader添加了一个新的protected方法findClass(),(在上面的源代码中也可以看见)。

    而在双亲委托模型未设计出前,用户去继承java.lang.ClassLoader的唯一目的就是为了重写loadClass()方法,因为JVM在进行类加载的时候,会调用加载器私有方法loadClassInternal(),而这个方法的唯一逻辑就是去调用自己的loadClass()方法。

    参考源码(java.lang.ClassLoader.java)

    私有只是用于虚拟机调用来加载类的入口:

    // This method is invoked by the virtual machine to load a class.

    private Class loadClassInternal(String name)

    throws ClassNotFoundException

    {

    // For backward compatibility, explicitly lock on 'this' when

    // the current class loader is not parallel capable.

    if (parallelLockMap == null) {

    synchronized (this) {

    return loadClass(name);

    }

    } else {

    return loadClass(name);

    }

    }

    而在JDK1.2双亲委托出现之后,不提倡用户再去覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法完成加载,在loadClass()方法父类加载失败,则会调用自己的findClass方法完成加载,这样就保证写出来的类加载器是符合双亲委托规则的。

    b. 二次破环是该双亲委托模弊端引起的。该模型能很好的解决各个类加载器对基础类的统一问题。因为它们总是作为被用户代码调用的API,但是问题就来了。如果基础类又要调用回用户的代码,该如何实现?。

    典型例子:JNDI服务,它的代码由启动类加载器加载(在rt.jar中),但JNDI目的就是对整个程序的资源进行几种管理和查找,需要调用由每个不同独立厂商实现并且部署在应用程序的ClassPath下的JNDI接口提供者的代码。但是在应用启动时候读取rt.jar包时候,是不认识这些三方厂商定义的类的,那么如何解决?

    java设计团队引入了一个新设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时候,还未设置,将会从父线程中继承一个。如果在应用程序全局范围都没有设置,默认是appClassLoader类加载器。

    class Thread implements Runnable {

    ...

    /* The context ClassLoader for this thread */

    private ClassLoader contextClassLoader;

    @CallerSensitive

    public ClassLoader getContextClassLoader() {

    if (contextClassLoader == null)

    return null;

    SecurityManager sm = System.getSecurityManager();

    if (sm != null) {

    ClassLoader.checkClassLoaderPermission(contextClassLoader,

    Reflection.getCallerClass());

    }

    return contextClassLoader;

    }

    //

    public void setContextClassLoader(ClassLoader cl) {

    SecurityManager sm = System.getSecurityManager();

    if (sm != null) {

    sm.checkPermission(new RuntimePermission("setContextClassLoader"));

    }

    contextClassLoader = cl;

    }

    /**

    * Returns a reference to the currently executing thread object.

    *

    * @return the currently executing thread.

    */

    public static native Thread currentThread(); //用于获取当前现在运行程序的线程

    ...

    }

    如何自定义类加载器

    那么我们如何实现自定义类加载呢,其实很简单,只要继承抽象的java.lang.ClassLoader类即可。然后重写findClass()方法,实现自己类加载器加载类的具体规则和实现。那我们为什么还要自己定义类加载器呢?好处多啊,在上文也说到了,具体下文说到。

    定义自已的类加载器分为两步:(其实也可以重写loadClass方法的,不过也就破化了双亲委托模型)

    继承java.lang.ClassLoader

    重写父类的findClass方法

    父类有那么多方法,为什么偏偏只重写findClass方法?

    因为JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。

    来具体实现一个实践实践看看:

    在c盘桌面放置clazz文件夹,里面由ClassTest.class文件。(可以加载本地,网络上的,数据库等等位置的类文件)

    public class ClassLoaderTest extends ClassLoader {

    @Override

    protected Class> findClass(String name) throws ClassNotFoundException {

    File file = getClassFile(name);

    try {

    byte[] bytes = getClassBytes(file);

    return defineClass(name,bytes, 0, bytes.length);

    } catch (Exception e) {

    e.printStackTrace();

    }

    return super.findClass(name);

    }

    private File getClassFile(String name) {

    // return new File(name);

    return new File("C:\\Users\\XianSky\\Desktop\\clazz\\ClassTest.class");

    }

    private static byte[] getClassBytes(File file) throws Exception {

    FileInputStream fis = new FileInputStream(file);

    ByteArrayOutputStream aos = new ByteArrayOutputStream(fis.available());

    byte[] bytes = new byte[fis.available()]; //使用fis.avaliable()方法确保整个字节数组没有多余数据

    fis.read(bytes);

    aos.write(bytes);

    fis.close();

    return aos.toByteArray();

    }

    public static void main(String[] args) throws Exception {

    ClassLoaderTest ct = new ClassLoaderTest();

    Class c = Class.forName("clazz.ClassTest", true, ct);

    System.out.println(c.getClassLoader());

    }

    }

    //输出

    clazz.ClassLoaderTest@15db9742

    可以看到获取本地file路径下的class文件的二进制字节码,然后使用自定义的类加载器进行加载,输出的就是自定义的类加载器。这种形式的类字节码加载过程很显然易见,是因为当前类加载器的所有父类加载器都查找不到该类字节流,所以最后是使用我们自定义的类加载实现加载的。

    2.可以使用findClass方法加载同样环境下的同一个类文件,也能达到不能类加载加载相同类也是不同的结果。

    在eclipse中项目中存在以下目录结构文件,与上述A不同的是,我们自定义的加载器的父类加载器AppClassLoader其实是可以加载到clazz.ClassTest类的,根据双亲委托加载想,那父类都能加载了,为什么第一个clazz.ClassTest的类加载器还是我们自定义的ClassLoaderTest呢?

    --projectName

    -src

    -clazz

    -ClassLoaderTest.java

    -ClassTest.java

    我们也可以加载同一个路径下的java类,重点看看程序运行输出的对象示例的内存地址。(当前开发工具eclipse编译后的类字节通过appClassLoader是可以加载得到的)

    public class ClassLoaderTest extends ClassLoader {

    @Override

    protected Class> findClass(String name) throws ClassNotFoundException {

    // File file = getClassFile(name);

    // try {

    // byte[] bytes = getClassBytes(file);

    // return defineClass(name,bytes, 0, bytes.length);

    // } catch (Exception e) {

    // e.printStackTrace();

    // }

    // return super.findClass(name);

    try{

    String fileName = name.substring(name.lastIndexOf(".")+1)+".class";

    InputStream is = getClass().getResourceAsStream(fileName);

    if(is ==null){

    return super.loadClass(name);

    }

    byte[] b = new byte[is.available()];

    is.read(b);

    return defineClass(name,b,0,b.length);

    }catch (Exception e){

    throw new ClassNotFoundException(name);

    }

    }

    private File getClassFile(String name) {

    // return new File(name);

    return new File(name);

    }

    private static byte[] getClassBytes(File file) throws Exception {

    FileInputStream fis = new FileInputStream(file);

    ByteArrayOutputStream aos = new ByteArrayOutputStream(fis.available());

    byte[] bytes = new byte[fis.available()]; //使用fis.avaliable()方法确保整个字节数组没有多余数据

    fis.read(bytes);

    aos.write(bytes);

    fis.close();

    return aos.toByteArray();

    }

    public static void main(String[] args) throws Exception {

    ClassLoaderTest ct = new ClassLoaderTest();

    Object obj = ct.findClass("clazz.ClassTest").newInstance();

    System.out.println(obj.getClass());

    System.out.println(obj.getClass().getClassLoader());

    Class c = Class.forName("clazz.ClassTest", true, ct);

    System.out.println(c.getClassLoader());

    System.out.println(clazz.ClassLoaderTest.class.getClassLoader());

    }

    }

    //输出

    class clazz.ClassTest

    clazz.ClassLoaderTest@6d06d69c

    clazz.ClassLoaderTest@6d06d69c

    sun.misc.Launcher$AppClassLoader@73d16e93

    可以看到,第一次使用自定义类加载findClass加载的class.ClassTest是我们自定义加载器的加载,最后那个clazz.ClassLoaderTest.class是appClassLoader加载,这就说明了我们自定义的类加载的父类加载器是可以访问查找到这个类,至于为什么会这样,我就根据自己的理解将加载class.ClassTest类时序图过程画出来:

    format,png

    myloader_process.png

    这里要注意的一个是:在双亲委托过程中,真正完成类加载工作的类加载器和启动这个加载过程的类加载器,可能不是同一个。真正完成类加载工作是通过defineClass来实现的;而启动类的加载过程是通过调用loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initating loader)。在JVM中,那个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。

    所以这里的clazz.ClassTest中,引导类加载器相当于类的初始加载器,而自定义的类加载是定义加载器。所以clazz.ClassTest.class.getClassLoader方法返回的是我们自定义的类加载。(这也仅仅是我自己的理解,仅供参考,也有可能不对啊,还需努力…)

    通过重写loadClass方法来自定义类加载器

    public class ClassLoaderTest2 {

    public static void main(String[] args) throws Exception{

    //通过重写loadClass方法来自定义类加载器

    ClassLoader myLoader = new ClassLoader(){

    @Override

    public Class> loadClass(String name) throws ClassNotFoundException {

    try{

    String fileName = name.substring(name.lastIndexOf(".")+1)+".class";

    InputStream is = getClass().getResourceAsStream(fileName);

    if(is ==null){

    return super.loadClass(name);

    }

    byte[] b = new byte[is.available()];

    is.read(b);

    return defineClass(name,b,0,b.length);

    }catch (Exception e){

    throw new ClassNotFoundException(name);

    }

    };

    };

    Object obj = myLoader.loadClass("clazz.ClassLoaderTest2").newInstance();

    System.out.println(obj.getClass());

    System.out.println(obj.getClass().getClassLoader());

    System.out.println(clazz.ClassLoaderTest2.class.getClassLoader());

    System.out.println(obj instanceof clazz.ClassLoaderTest2);

    }

    //output:

    class clazz.ClassLoaderTest2

    clazz.ClassLoaderTest2$1@7852e922

    sun.misc.Launcher$AppClassLoader@73d16e93

    false

    }

    可以看见,不同的类加载器加载相同的一个类,会对instanceof造成一定的影响。虽然存在了两个一样的ClassLoaderTest2,但是是两个不同类加载器加载的。一个是应用类加载器加载,另外一个是我们自定义类加载器加载的,所以是不同的两个类。

    类加载器与类标识的命名空间?

    也就是说,在JVM搜索类的时候,如何判断两个相同全限定名的类是否是同一个类?也就会对后面判断该类是否已经加载的流程有重要影响了。

    JVM在判定两个class是否相同时候,不仅要判断两个类是否相同(equals,hashCode,全限定名..),还要判断该类是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才会认为这两个class是相同。

    关于类加载器和类共同标识命名空间和类加载的共享和隔离问题,可以拿tomcat来作为例子说明。tomcat内部由自己定义的类加载器,还可以通过tomcat安装目录下的catalina.properties文件灵活配置应用共享类库,和应用工之间单独使用的类库。这个,就放在下次说了,我也好好看看,思考思考。。。

    展开全文
  • 上一篇文章我们知道了类加载器通过组合方式实现了双亲委托,那么类又是如何具体加载的呢?类加载过程回顾下类加载的源码,如下图:上一篇文章中自定义的类加载器直接重写了loadClass,打乱了类加载的双亲委托机制,...

    上一篇文章我们知道了类加载器通过组合方式实现了双亲委托,那么类又是如何具体加载的呢?

    类加载过程

    回顾下类加载的源码,如下图:

    上一篇文章中自定义的类加载器直接重写了loadClass,打乱了类加载的双亲委托机制,今天需要在不破坏这个机制下自定义!

    从源码中可以看出如果在parent最终不能加载的情况下,会来到第3步由当前加载器来加载,而第3步最关键的代码就是“ c = findClass(name)”。我们先找到应用类加载器Launcher.AppClassLoader并没有findClass(name)方法,不过它的父类URLClassLoader实现了findClass方法,方法代码如下图:

    可以看到这个方法在获取要加载类的正真文件,继续跟definClass方法最后会调用ClassLoader.defineClass,在这个方法里面会调用一个defineClass1的本地方法!

    自定义类加载器

    从以上分析出我们如果想自定义一个类加载器,同时不破坏双亲委托机制,最关键的是重写findClass方法,所以我们就可以自定义实现类加载器了,代码如下图:

    自定义的classLoader给他新增了一个名称属性,并且重写了findClass,同时在findClass方法第一行打印了类加载名称和加载的类名称。

    写一个测试方法并运行,运行结果如下图:

    根据打印结果可以判断并没有运行findClass方法,这是什么原因呢?

    在自定义的类加载器中构造函数没有显示的执行super()就会默认执行父类的默认构造函数,也就是ClassLoader的默认构造函数,它设置的parent是AppClassLoader。

    而我们加载的类“com.dggcc.test.classload.ClassLoadTest”在当前工程下,是可以被AppClassLoader所加载的,所以最终并不会执行MyClassLoader的findClass方法。

    对类加载器进行改造,使他去加载指定文件夹下的文件,改造后代码如下图:

    在加载器中添加一个属性path,同时查找class文件的时候只在这个文件下加载,测试代码如下图:

    首先要把ClassLoadTest.class移到你想加载的路径下,然后要把把项目下对应的ClassLoadTest.class文件删除,否则在加载的时候AppClassLoader还是能找到“com.dggcc.test.classload.ClassLoadTest”对应的class文件,能够加载成功。删除后由于系统自带的加载器都不能加载这个类,最终由我们自定义的类加载加载,从打印结果可以验证我们的结论!

    测试验证结论

    通过自定义类加载我们就可以很方便的验证之前的一些结论,测试代码如下图:

    可以看到myclassLoad1对同一个类加载2次,但是真正只加载了一次,并且根据类的hashCode也可以看出来是同一个class对象。而如果再次new一个myclassLoad2出来对同一个class文件加载,就再次加载了一次,并且hashCode也不同不是同一个class对象!

    可以得出结论:同一个类只会被同一个类加载器加载一次,不同的类加载器加载同一个class文件加载出来的class对象也不相同!

    我们对代码再次改造进行测试,改造代码如下图:

    新建了一个构造函数用来设置parent属性,通过结果可以看到类只被加载了一次,这是因为myclassLoad1作为了myclassLoad2的parent。所以myclassLoad2在加载类的时候会去myclassLoad1找是否加载。如果加载了myclassLoad2就不会再加载了!

    如果在myclassLoad2之前不加载类,测试代码如下图:

    可以看到正真加载类的还是myclassLoad1,也就是双亲委托机制!

    总结

    结合上一篇我们知道重写loadClass方法可能会破坏双亲委托机制,而只重写findClass则不会,同时可以实现去加载其他地方的class文件。

    通过自定义类加载我们再次验证了类的加载机制,同一个类只会被同一个类加载器加载一次,不同的类加载器加载同一个class文件加载出来的class对象也不相同! 并且再次验证了双亲委托加载类。

    Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

    展开全文
  • 使用SPI server provider模式的JDBC JAXP都是破坏了双亲委托模式的,在核心类库rt.jar的加载过程中需要加载第三方厂商的类,直接指定使用线程上下文类加载器也就是应用程序类加载器来加载这些类;Tomcat中的web 容器...
  • Java中类加载采用的是双亲委托机制,为什么要这样处理?又是怎样实现的呢?接下来简单说说我的理解。总览图 首先我们要知道双亲委托机制是怎么样的,总览图如下图: Java中类的加载器主要有2种,Java虛拟机自带的...
  • 很多情况下,不得以...如果是一个遵循java已经规定好的机制的classloader(双亲委托以及加载依赖类的classloader继续加载剩下的类)。直接继承classloader就可以。(一般选择urlclassloder,他帮你实现了不少功能)。...
  • Java双亲委托

    2020-08-21 00:19:20
    双亲委托类加载器的种类类加载的过程 类加载器的种类 1.启动类加载器(bootstrapClassLoader) 这个类加载器不是由java代码实现的 这个类加载器专门加载java系统中最重要最基本的类 2.扩展类加载器(ExtClassLoader) 这...
  • jvm如何打破双亲委托机制

    万次阅读 2020-07-09 20:03:51
    打破双亲委托机制 重写父类ClassLoader的loadClass方法 package com.morris.jvm.classloader; public class BreakDelegateClassLoader extends MyClassLoader { @Override protected Class<?> loadClass...
  • 1.什么是双亲委托模式?2.如何打破双亲委托模式?3.打破类加载模式有什么应用和意义?黄桂钊:90,谈谈类加载过程的双亲委托机制?​zhuanlan.zhihu.comJava 中的双亲委派的“双”怎么理解 ?​...
  • 破环双亲委托机制

    2020-06-27 10:03:08
    在之前我们给出的方案都是绕过应用加载器的方式,并没有避免一层一层的委托,那么有没有什么办法可以绕过这种双亲委托模型呢? 很庆幸,JDK提供的双亲委托机制并非是一个强制的模型,程序员可以对其进行灵活的发挥破...
  • 通俗易懂的双亲委托

    2019-10-08 21:14:20
    双亲委托模型 一、什么是双亲委托 ​ 双亲委派模型,就是如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托...
  • ART双亲委托机制杂记

    2020-02-24 11:46:55
    1.ART双亲委托机制: PathClassLoader,DexClassLoader --继承自--> BaseDexClassLoader --继承自--> ClasssLoader protected Class<?> loadClass(String name, boolean resolve) throws Clas...
  • android classloader双亲委托模式

    万次阅读 2017-03-23 17:20:06
    概述ClassLoader的双亲委托模式:classloader 按级别分为三个级别:最上级 : bootstrap classLoader(根类加载器) ; 中间级:extension classLoader (扩展类加载器) 最低级 app classLoader(应用类加载器)。...
  • 在加载类的时候,Java建议使用双亲委托这种模型,流程其实很简单: 加载器收到加载类的请求后,判断要加载的类是否已经存在 如果不存在的话,委托给父类加载器 父类加载器如果还有父类加载器,重复2 如果父类加载器...
  • ClassLoader的双亲委托模式:classLoader按级别分为三个级别:最上级:bootstrap classLoader(根类加载器);中间级:extension classLoader(扩展类加载器);最低级 app classLoader(应用类加载器)。 根类加载器...
  • 什么是双亲委托机制 要了解双亲委托机制要先了解JAVA的类加载器。 类加载器 ​ JVM设计者把类加载阶段中的“通过’类全名’来获取定义此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己...
  • 双亲委托机制介绍 双亲委托机制有时也称父委托机制,当一个类调用loadClass之后并不会直接将其加载,而是先交给当前类加载器的父加载器尝试加载,知道最顶层的父加载器,然后再一次向下进行加载。 loadClass方法源码...
  • ART的classloader,继续采用双亲委托模式 ANDROID classloader:https://www.jianshu.com/p/a620e368389a 双亲委派模型 2016年01月02日 15:34:17 12065 说道双亲委派模型,就要从类加载器说起...
  • 双亲委托模式的简单介绍
  • 大家可能都听说过Java的类加载机制,是双亲委托模式,听起来有点神秘,但是为什么会叫这个名称呢?  今天我们就来简单的介绍下!  所谓双亲委托,  1. 就是首先当前ClassLoader判断该Class是否已经加载;  2. ...
  • JVM ----双亲委托机制

    2021-04-28 17:56:20
    双亲委托机制解释:Java虚拟机对class文件采取的是按需加载的方式,也就是说需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委托模式即把请求交由...
  • 虚拟机类加载机制:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机...双亲委派模型:某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类...
  • 虽然前面把class文件的产生到加载使用流程说了一遍,但是还是想具体看看classLoader的双亲委托具体是如何运行的,有什么利弊。 还有想看看不同类加载器的不同命名空间带来那些好处和实际有那些应用?并且想对...
  • 1、双亲委托机制 在双亲委托机制中,各个加载器按照父子关系形成了树形结构(逻辑上的树形结构,并非继承关系),除了根类加载器之外,其余的类加载器都有且只有一个父加载器。 双亲委托机制的工作过程: 如果一个...
  • 月薪过万必会的:双亲委托模型

    千次阅读 多人点赞 2019-12-24 12:00:33
    要想深入了解JVM,双亲委托模型是绕不过的,也是中高级开发面试时经常问到的。了解了它,你就会对JDBC、SPI、OSGi等的类加载机制有更深一层的理解。
  • 本文深入源码,解析双亲委托机制的实现原理
  • 目录前言DexART与Dalvikdexopt与dexaotClassLoader介绍双亲委托机制总结 前言 一个Java程序,会通过javac编译成class文件,然后通过虚拟机加载(ClassLoader)到方法区,执行引擎会执行这些字节码,并翻译成操作系统...
  • 双亲委托加载机制 当我们使用java命令运行一个类时,并不是简单的运行的.class文件,而是经过一系列的判断之后才加载类。以下将介绍Java的双亲委托类加载机制。 1. 加载器分类 启动类加载器(bootstrap class ...

空空如也

空空如也

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

双亲委托