精华内容
下载资源
问答
  • 双亲委托
    2022-04-28 14:47:13

    有两种类型的类加载器

    1. Java虚拟机自带的加载器
      1. 根类加载器(Bootstrap)
      2. 扩展类加载器(Extension)
      3. 系统(应用)类加载器(System)AppClassLoader
    2. 用户自定义的类加载器
      1. Java.lang.ClassLoader的子类
      2. 用户可以定制类的加载方式

    类加载器并不需要等到某个类被主动使用时再加载它

    JVM规范允许类加载器再预料某个类将要被使用时就预先加载它,如果再预先加载的过程中遇到了.class文件缺失或者存在的错误,类加载器必须再程序首次主动使用该类时报告错误(LinkageError错误)

    如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

    JVM类的初始化步骤

    1. 假如这个类还没有被加载和连接,那就先进行加载和连接
    2. 假如类存在直接父类,并且这个父类还没有被初始化,那么就先初始化直接父类
    3. 假如类中存在初始化语句,那就依次执行这些初始化语句

    双亲委托模型:

    1. 当一个类被使用时会先去找到系统类加载器AppClassLoader是否存在。如果存在则返回,不存在则找到它的父加载器ExtClassLoader进行查找是否存在。如果存在则返回,不存在则在去找父加载器BootstrapClassLoader进行查找。如果存在则返回,不存在则由BootstrapClassLoader根类加载器尝试加载。成功则返回,失败则退回子加载器ExtClassLoader去尝试加载。成功则返回,失败则由子类加载器AppClassLoader进行加载,成功则返回,失败报错。

    若有一个类加载器能够成功加载Test类,那么这个类加载器被称为定义类加载器,所有能够成功返回Class对象引用的类加载器(包括定义类加载器)都被成为初始类加载器

     * <p> {@code Class} objects for array classes are not created by class

     * loaders, but are created automatically as required by the Java runtime.

     * The class loader for an array class, as returned by {@link

     * Class#getClassLoader()} is the same as the class loader for its element

     * type; if the element type is a primitive type, then the array class has no

     * class loader.

    注意数组类型并不是通过类加载器加载得到,而是通过JVM动态创建的,当数值类调用getClassLoader(),将获得数值类型的类加载器,当数组的类型是原始类型则没有类加载器。

    例:

    {
        String[] strings = new String[5];  
        strings.getClass().getClassLoader(); //结果为null;
        MyTest[] mytests = new MyTest[2]; 
        mytest.getClass().getClassLoader(); //结果为AppClassLoader 
    }

    类加载器的命名空间

    1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器的类组成。
    2. 再同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
    3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

    注意父加载器加载的类无法访问子加载器加载的类,子加载器能够访问父加载器加载的类

    类的卸载

    1. 当MySample类被加载、连接和初始化后,它的生命周期就开始了。当代表MySample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,MySample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期
    2. 一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期
    3. 由Java虚拟机自带的类加载器加载的类,在虚拟机的生命周期中,始终不会被卸载。包括根类加载器、扩展类加载器和系统类加载器。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的Class对象,因此这些Class对象始终是可触及的
    4. 由用户自定义的类加载器所加载的类是可以被卸载的

    类双亲委托模型的好处:

    1. 可以确保java核心库的类型安全:所有的Java应用都至少会引用java.lang.Object这个类会被加载到java虚拟机中;如果这个加载过程由java应用自己的类加载器所完成的,那么很可能就会在JVM中存在多个版本的java.lang.Object,而这些类之间还是不兼容不可见的(正式命名空间在发挥作用)。借助于双亲委托机制,java核心类库中的类的加载工作都是由启动类(根类)加载器统一完成的,从而确保了java应用中确保了java所使用的都是统一版本的java核心类库,他们都是兼容的
    2. 确保java核心类库提供的类不会被自定义类所替代
    3. 不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间,相同名称的类可以并存在java虚拟机中只需要通过不同的类加载器加载即可,不同类加载器所加载的类之间是不兼容的,这就相当于java虚拟机内部创建了一个又一个相互隔离的java类空间,这个技术在很多框架中得到了实际应用
    更多相关内容
  • 双亲委托寻找 & 加载; 二、ClassLoader的类型和继承关系 2.1、Java中的ClassLoader image.png ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能; SecureClassLoader继承了抽象类ClassLoader; 它并不是...

    一、前言

    无论是做Java开发,还是Android开发,ClassLoader是少不了打交道的。即便我们不围绕它做点啥,它也默默的在为我们做着事情。

    顾名思义,ClassLoader就是Java编译成Class文件后,通过它加载到JVM中来运行的。

    Android本身有着和Java同源的血统,因此,ClassLoader的核心功能机制是不会变的:

    IO读取Class文件,解析,分配栈、堆,加载到永久代(Perm区);

    双亲委托寻找 & 加载;

    二、ClassLoader的类型和继承关系

    2.1、Java中的ClassLoader

    27d08bd27d0a

    image.png

    ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能;

    SecureClassLoader继承了抽象类ClassLoader;

    它并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了安全性。

    URLClassLoader类继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源;

    ExtClassLoader和AppClassLoader都继承自URLClassLoader,它们都是Launcher 的内部类;

    Launcher 是Java虚拟机的入口应用,ExtClassLoader和AppClassLoader都是在Launcher中进行初始化的。

    2.2、Android中的ClassLoader

    27d08bd27d0a

    image1.png

    ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。BootClassLoader是它的内部类;

    SecureClassLoader继承了抽象类ClassLoader;

    它并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了安全性。

    URLClassLoader类继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源;

    InMemoryDexClassLoader是Android8.0新增的类加载器,继承自BaseDexClassLoader,用于加载内存中的dex文件;

    BaseDexClassLoader继承自ClassLoader,是抽象类ClassLoader的具体实现类,PathClassLoader和DexClassLoader都继承它;

    2.3、Android各ClassLoader区别

    BootClassLoader,它是 Android 中最顶层的 ClassLoader,继承自ClassLoader,C代码编写,不能被继承和修改。

    PathClassLoader:只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器。

    DexClassLoader:可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点。

    三、类加载机制(双亲委托机制保证类的唯一性)

    先来看看 DexClassLoader 和 PathClassLoader,只有继承,并没有重载 loadClass 这个类加载方法:

    package dalvik.system;

    public class DexClassLoader extends BaseDexClassLoader {

    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {

    super(dexPath, null, librarySearchPath, parent);

    }

    }

    ///

    package dalvik.system;

    public class PathClassLoader extends BaseDexClassLoader {

    public PathClassLoader(String dexPath, ClassLoader parent) {

    super(dexPath, null, null, parent);

    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {

    super(dexPath, null, librarySearchPath, parent);

    }

    }

    loadClass 在 BaseDexClassLoader 中也没有重载,实际的实现,还是在 JDK的 ClassLoader 中:

    protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    // 1. 先检查该类是否已经被当前 XxxClassLoader 加载

    Class> c = findLoadedClass(name);

    if (c == null) {

    try {

    // 2. 如果没有加载,且有父类加载器,就让父类重复该过程

    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) {

    // 3. 如果没有找到, 调用自身查找方法,该方法需要子类Override实现,

    // 否则抛异常:ClassNotFoundException

    c = findClass(name);

    }

    }

    return c;

    }

    protected final Class> findLoadedClass(String name) {

    ClassLoader loader;

    if (this == BootClassLoader.getInstance())

    loader = null;

    else

    // DexClassLoader/PathClassLoader -> BaseDexClassLoader -> Native..

    loader = this;

    return VMClassLoader.findLoadedClass(loader, name);

    }

    回顾上面3个步骤:

    除了顶层类加载器外,其他的类加载器都有自己的父类加载器,在加载类时首先判断这个类是否被加载过,如果已经加载则直接返回;

    如果未被加载过,则先尝试让父加载器进行加载,最终所有加载请求都会传递给顶层的加载器中;

    当父加载器发现未找到所需的类而无法完成加载请求时,子加载器的findClass方法中进行加载;

    这就是双亲委托机制:当未加载类时,永远先委托父类去查找 / 加载!

    不要觉得这是给父类『找麻烦』,这实际上是一种机制:保证一个类的唯一性!

    双亲委托机制可以保证同一个全限定名的class,可以被同一个ClassLoader加载!

    类的唯一性判断:

    JVM 判定两个 class 是否相同:

    判断两个类名是否相同;

    判断是否由同一个类加载器实例加载;

    只有两者同时满足的情况下,JVM 才认为这两个 class 是相同的。

    注:同一个class,如果被两个不同的 ClassLoader 实例所加载,JVM 也会认为它们是两个不同 class。

    四、热修复技术

    一个APK,可能有一个或多个 dex 文件,如果 classes.dex 和 classes1.dex 有重复的类,当系统加载到这个重复类时,会如何?

    一个 ClassLoader 可以有多个 dex 文件(存在 dexPathList 中),每个 dex 文件是一个 Element,多个 dex 文件排列成一个有序的数组(dexElements),当查找某个类时,会按顺序遍历 dex 文件,如果找到则返回;找不到则继续从下一个 dex 中查找。

    因此靠前的 dex 文件会优先被选择(多个 dex 中有重复的类,则最靠前的被加载,后面的不会再被加载,因为有双亲委托机制)。

    4.1、基于 dex 分包方案

    基于DEX分包方案,使用了多DEX加载的原理。

    大致的过程就是:把bug方法修复以后,放到一个单独的dex里,插入到dexElements数组的最前面,让虚拟机去加载修复完后的方法。

    当patch.dex中包含Test.class时就会优先加载,在后续的DEX中遇到Test.class的话就会直接返回而不去加载,这样就达到了修复的目的。

    详细步骤如下:

    (1)通过获取到当前应用的Classloader,即为BaseDexClassloader;

    (2)通过反射获取到他的DexPathList属性对象pathList;

    (3)通过反射调用pathList的dexElements方法把patch.dex转化为Element[];

    (4)两个Element[]进行合并,把patch.dex放到最前面去;

    (5)根据类的加载机制,加载Element[],达到修复目的。

    特点是产很灵活,对开发者透明,但是不支持即时生效,必须通过重启才能生效。另外当插入的dex比较多的时候影响启动性能比较大。

    4.2、阿里的 AndFix 方案

    AndFix提供了一种运行时在Native修改Filed指针的方式,实现方法的替换,达到即时生效无需重启,对应用无性能消耗的目的。

    AndFix对ART设备同样支持,具体的过程与Dalvik相似。

    替换流程如下:

    Dalvik设备——>Native层找到被替换的类——>将类的状态设置为初始化完毕——>得到新旧方法的指针——>操作指针,指针指向新的替换方法——>完成新的方法替换。

    特点是及时性强,不需要重启手机,生成的差量包小,性能损耗小,缺点是是不支持新增字段,也不支持对资源的替换,和ROM关系很大,一旦厂商修改ROM下发就失败了。

    展开全文
  • 双亲委托机制 某个特定的类加载器接收到类加载的请求时,会将加载任务委托给自己的父类,直到最高级父类引导类加载器(Bootstrap ClassLoader),如果父类能够加载就加载,不能加载则返回到子类进行加载。...

    类加载器简述

    类加载器继承加载类路径
    引导)Bootstrap ClassLoader%JAVA_HOME%\jre\lib\下的jar(jre核心库)
    扩展)ExtClassLoaderextends URLClassLoader%JAVA_HOME%\jre\lib\ext目录下的jar
    应用)AppClassLoaderextends URLClassLoader加载当前引用的classpath的所有类

    *Bootstrap ClassLoader是由C/C++*编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类


    双亲委托机制

    某个特定的类加载器接收到类加载的请求时,会将加载任务委托给自己的父类,直到最高级父类引导类加载器(Bootstrap ClassLoader),如果父类能够加载就加载,不能加载则返回到子类进行加载。如果都不能加载则报错:ClassNotFoundException


    类加载器加载类对象的时候是

    逆向(查询)委托是否已经加载,已加载就采用,未加载就继续逆向去问父加载器;直到加载到引导类加载器(Bootstrap ClassLoader)

    顺序去尝试加载,加载不到就继续顺下去给子加载器尝试加载的过程;最后都没有就报:ClassNotFoundException

    本质上是一个递归的过程,看下面的类加载器的三个方法的说明。


    图片引用:https://blog.csdn.net/briblue/article/details/54973413

    在这里插入图片描述


    三个重要函数

    (问:自定义类加载器怎么实现,其中哪个方法走双亲委派模型,(实现findclass方法,一般用defineclass用来加载外部类类对象),如何才能不走双亲委派。(重写loadclass方法))


    三个重要函数:loadClass,findClass,defineClass

    • loadClass:调用父类加载器的loadClass,加载失败则调用自己的findClass方法。(对应逆序委托
    • findClass:根据名称读取文件存入字节数组。(对应顺序加载
    • defineClass:把一个字节数组(二进制字节码类文件)转为Class对象

    执行流程

    最开始我们通过main中的(My)Classloader.loadClass()去加载类对象(不会执行静态代码块代码),loadClass这个方法会不断递归去调用(父类类加载器)parent.loadClass(对应双亲委托机制的逆向查询委托过程),loadClass一直向父类询问,直到找到或者父类为Bootstrap ClassLoader或空,然后执行findClass方法。如果父类没有抛出FileNotFoundException,这个时候返回其子类,已经找到;如果爸爸没有,抛出了FileNotFoundException,那么子类将会执行findClass,直到最开始发起loadClass方法的类加载器。这个过程本质上是一个递归的过程。


    对应流程图:

    在这里插入图片描述


    对应源码:

    在这里插入图片描述


    为什么要自定义ClassLoader

    因为系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader.

    而且我们可以根据自己的需求,对class文件进行加密和解密

    (1)加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载。

    (2)从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。

    (3)以上两种情况在实际中的综合运用:比如你的应用需要通过网络来传输 Java 类的字节码,为了安全性,这些字节码经过了加密处理。这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出在Java虚拟机中运行的类。


    加密/解密类对象

    加密工具类

    采取二进制Class文件进行异或运算进行加密。

    /**
     * 类Class加密工具类
     * **/
    public class EncryptedClassUtil {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            encrpt("C:\\Users\\cbry\\Desktop\\Hello.class", "C:\\Users\\cbry\\Desktop\\files\\Hello.class");
        }
    
        public static void encrpt(String src, String dest) {
    
            FileInputStream fis = null;
            FileOutputStream fos = null;
    
            try {
                fis = new FileInputStream(src);
                fos = new FileOutputStream(dest);
    
                int tempData = -1;
                while((tempData = fis.read()) != -1) {
                    fos.write(tempData^0xff);	//异或,0xff即为:11111111 , int8个字节对每个字节按位取反
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
    
                try {
                    if(fis != null) {
                        fis.close();
                    }
                    if(fos != null) {
                        fos.close();
                    }
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
    
            }
        }
    }
    
    

    加密前

    使用jd-gui反编译Class文件, jd-gui下载地址

    在这里插入图片描述

    加密后

    位运算加密后输出的Hello.class文件无法被反编译。


    自定义类加载器

    解密二进制类文件

    重写findClass()方法以及增加getClassData方法:

    package com.cbry.classloader;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class MyClassLoader extends ClassLoader {
        //指定路径
        private String rootPath;
    
    
        public MyClassLoader(String rootPath) {
            this.rootPath = rootPath;
        }
    
        /**
         * 重写findClass方法
         *
         * @param name The binary name of the class
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            System.out.println("findClass: " + name);
            Class<?> c = null;
            // 获取该class文件字节码数组
            byte[] classData = getClassData(name);
    
            if (classData != null) {
                try {
                // 将class的字节码数组转换成Class类的实例
                c = defineClass(name, classData, 0, classData.length);
                } catch (ClassFormatError e){	// 加载错误
                    e.printStackTrace();
                    c = null;
                }
            }else{
                throw new ClassNotFoundException();	// 如果字节数组为空,抛出异常
            }
            return c;
        }
    
        /**
         * 将class文件转化为字节码数组
         * @return
         */
        private byte[] getClassData(String name) {
    System.out.println("getClassData: " + name);
            name = name.replaceAll("\\.", "/");
    
            File file = new File(rootPath + "/" + name + ".class");
            if (file.exists()) {
                FileInputStream fis = null;
                ByteArrayOutputStream bos = null;
                try {
                    fis = new FileInputStream(file);
                    bos = new ByteArrayOutputStream();
    
                    int tempData = -1;
                    while((tempData = fis.read()) != -1) {
                        bos.write(tempData^0xff);	//异或,0xff即为:11111111 , int8个字节对每个字节按位取反
                    }
    
                    return bos.toByteArray();
    
                } catch (IOException e) {
                    e.printStackTrace();
                    return null;
                } finally {
                    try {
                        if(fis != null) {
                            fis.close();
                        }
                        if(bos != null) {
                            bos.close();
                        }
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
    
            } else {
                return null;
            }
    
        }
    }
    
    

    主进程

        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
            MyClassLoader mc = new MyClassLoader("C:\\Users\\cbry\\Desktop\\files\\");
            Class<?> c = mc.loadClass("com.test.Hello");
            Object obj = c.newInstance();
            Method helloMethod = c.getDeclaredMethod("sayHello", null);
            helloMethod.invoke(obj, null);
        }
    

    在这里插入图片描述


    使用Hello直接在根目录下加载文件报错:

    在这里插入图片描述

    D:\eclipse-java-oxygen-R-win32-x86_64\JDK\bin\java.exe "-javaagent:D:\IntelliJ\IntelliJ IDEA Community Edition 2021.1\lib\idea_rt.jar=57447:D:\IntelliJ\IntelliJ IDEA Community Edition 2021.1\bin" -Dfile.encoding=UTF-8 -classpath D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\charsets.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\deploy.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\access-bridge-64.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\cldrdata.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\dnsns.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\jaccess.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\jfxrt.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\localedata.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\nashorn.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\sunec.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\sunjce_provider.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\sunmscapi.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\sunpkcs11.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\zipfs.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\javaws.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\jce.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\jfr.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\jfxswt.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\jsse.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\management-agent.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\plugin.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\resources.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\rt.jar;D:\A学习代码\leetcode\JVM\bin com.cbry.classloader.MyClassLoader
    Exception in thread "main" java.lang.NoClassDefFoundError: Hello (wrong name: com/test/Hello)
    	at java.lang.ClassLoader.defineClass1(Native Method)
    	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
    	at com.cbry.classloader.MyClassLoader.findClass(MyClassLoader.java:35)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    	at com.cbry.classloader.MyClassLoader.main(MyClassLoader.java:86)
    

    forName和ClassLoader

    lass.forName(“className”)

    其实这种方法调运的是:Class.forName(className,true,ClassLoader.getCallerClassLoader())方法

    • 参数1:classNmae,需要加载类的名称

    • 参数2:true,是否对class进行初始化(需要序列化initialize)

    • 参数3:classLoader,对应的类加载器


    ClassLoader.laodClass(“className”)

    其实这种方法调用的是:ClassLoader.loadClass(name,false)方法

    • 参数1:name,需要加载的类的名称

    • 参数2: false,这个类加载以后是否需要去连接(不需要linking)

    可见Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块

    而ClassLoader只干了一件事情,就是讲.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static


    参考博文

    自定义类加载器

    Java类加载器 自定义加密解密

    展开全文
  • ClassLoader的双亲委托模式:classLoader按级别分为三个级别:最上级:bootstrap classLoader(根类加载器);中间级:extension classLoader(扩展类加载器);最低级 app classLoader(应用类加载器)。 根类加载器...

    ClassLoader的双亲委托模式:classLoader按级别分为三个级别:最上级:bootstrap classLoader(根类加载器);中间级:extension classLoader(扩展类加载器);最低级 app classLoader(应用类加载器)。

    根类加载器(bootstrap classLoader):该加载器没有父加载器。它负责加载虚拟机的核心类库,如Java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。

    扩展(Extension)类加载器:它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。

    系统(System)类加载器:也称为应用类加载器,他的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,他是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。
    父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。

    对于java来说,java虚拟机要讲被用到的java类文件通过classLoader加载到JVM内存中,而这个classLoader就是bootstrap classLoader。而对于APP而言,首先请求app级来加载,继而请求extension classLoader,最后启动bootstrap classLoader。

    自定义ClassLoader

    在这里插入图片描述

    ```c
    public class MyClassLoader extends ClassLoader {
    
        //类加载器名称
        private String name;
        //加载类的路径
        private String path = "D:/";
        private final String fileType = ".class";
        public MyClassLoader(String name){
            //让系统类加载器成为该 类加载器的父加载器
            super();
            this.name = name;
        }
    
        public MyClassLoader(ClassLoader parent, String name){
            //显示指定该类加载器的父加载器
            super(parent);
            this.name = name;
        }
    
        public String getPath() {
            return path;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    
        @Override
        public String toString() {
            return this.name;
        }
    
        private byte[] loaderClassData(String name){
            InputStream is = null;
            byte[] data = null;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            this.name = this.name.replace(".", "/");
            try {
                is = new FileInputStream(new File(path + name + fileType));
                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 data;
        }
    
        @Override
        public Class<?> findClass(String name){
            byte[] data = loaderClassData(name);
            return this.defineClass(name, data, 0, data.length);
        }
    
        public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
            //loader1的父加载器为系统类加载器
            MyClassLoader loader1 = new MyClassLoader("loader1");
            loader1.setPath("D:/lib1/");
            //loader2的父加载器为loader1
            MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
            loader2.setPath("D:/lib2/");
            //loader3的父加载器为根类加载器
            MyClassLoader loader3 = new MyClassLoader(null, "loader3");
            loader3.setPath("D:/lib3/");
    
            Class clazz = loader2.loadClass("Sample");
            Object object = clazz.newInstance();
        }
    }
    public class Sample {
    
        public Sample(){
            System.out.println("Sample is loaded by " + this.getClass().getClassLoader());
            new A();
        }
    }
    public class A {
    
        public A(){
            System.out.println("A is loaded by " + this.getClass().getClassLoader());
        }
    }
    ```
    

    每一个自定义ClassLoader都必须继承ClassLoader这个抽象类,而每个classLoader都会有一个parent ClassLoader,我们可以看一下classLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前ClassLoader的parent,注意,这个parent不是指的被继承的类,而是在实例化该classLoader时指定的一个classLoader,如果这个parent为null,那么就默认该classLoader的parent是bootstrap classLoader。

    上面讲解了一下classLoader的作用以及一个最基本的加载流程看,接下来我们说说classLoader使用了双亲委托模式进行类加载。

    ClassLoader双亲委托模式

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

    为了更好的理解双亲委托模式,我们先自定义一个ClassLoader,假设我们使用这个自定义的ClassLoader加载 java.lang.String,那么这里String是否会被这个ClassLoader加载呢?

    事实上java.lang.String这个类并不会被我们自定义的classloader加载,而是由bootstrap classloader进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为在任何一个自定义ClassLoader加载一个类之前,它都会先 委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader无法加载成功后,才会由自己加载。而在上面的例子中,因为 java.lang.String是属于java核心API的一个类,所以当使用自定义的classloader加载它的时候,该 ClassLoader会先委托它的父亲ClassLoader进行加载(bootstrap classloader),所以并不会被我们自定义的ClassLoader加载。

    我们来看一下ClassLoader的一段源码:

    ```c
    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{  
             // 首先检查该name指定的class是否有被加载  
             Class c = findLoadedClass(name);  
             if (c == null) {  
                 try {  
                     if (parent != null) {  
                         //如果parent不为null,则调用parent的loadClass进行加载  
                         c = parent.loadClass(name, false);  
                     }else{  
                         //parent为null,则调用BootstrapClassLoader进行加载  
                         c = findBootstrapClass0(name);  
                     }  
                 }catch(ClassNotFoundException e) {  
                     //如果仍然无法加载成功,则调用自身的findClass进行加载              
                     c = findClass(name);  
                 }  
             }  
             if (resolve) {  
                 resolveClass(c);  
             }  
             return c;  
        }
    ```
    

    使用双亲委托模式优点
    那么我们使用双亲委托模式有什么好处呢?
    因为这样可以避免重复加载,当父亲已近加载了该类的时候,就没有必要子ClassLoader再加载一次。
    考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已近在启动的时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader

    Android ClassLoader简介

    展开全文
  • 双亲委托 一个类加载器查找class和resource时,是通过“委托模式”进行的。 它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ...
  • 初始化顺序先后顺序是:静态成员变量、成员变量、构造方法。
  • 虽然前面把class文件的产生到加载使用流程说了一遍,但是还是想具体看看classLoader的双亲委托具体是如何运行的,有什么利弊。还有想看看不同类加载器的不同命名空间带来那些好处和实际有那些应用?并且想对...
  • 双亲委托机制

    2021-04-04 14:50:41
    文章目录1 双亲委托机制介绍2 破坏双亲委托机制 1 双亲委托机制介绍 类加载器最重要的机制——双亲委托机制,有时候也称为父委托机制。当一个类加载器被调用了loadClass之后,它并不会直接将其加载,而是先交给当前...
  • 双亲委托机制是为了保证一个 Java 类在 JVM 中是唯一的,假如你不小心写了一个与 JRE 核心类同名的类,比如 Object 类,双亲委托机制能保证加载的是 JRE 里的那个 Object 类,而不是你写的 Object 类。这是因为 ...
  • 我们经常会遇到ClassNotFound异常,表明JVM在尝试加载某类时失败了...但在这之前,我们有必要预习一下JVM的类加载机制,我会先回答一下一开始抛出来的问题,接着再谈谈Tomcat的类加载器如何打破Java的双亲委托机制。 J
  • 双亲委托模型

    2022-04-09 20:50:55
    (1)如果一个类加载器收到了类加载请求,它并不会自己先加载,而是把这个请求委托给父类的加载器去执行 (2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的引导类加载器; ...
  • 目录前言DexART与Dalvikdexopt与dexaotAndroid N(7.0)混合编译ClassLoader介绍双亲委托机制双亲委托机制原理使用双亲委托机制目的1.安全。防止核心API库被篡改。2.避免重复加载。当一个类被父类加载器加载过的时候,...
  • 双亲委托模式

    2021-11-30 21:24:53
    类加载的双亲委托模式 类加载器的作用 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。...
  • 写在前面 ...双亲委托机制3.总结 1.类加载器 1.1 类加载器作用: 类加载器负责从文件或者网络中加载Class信息,加载的类信息存放于方法区的内存空间。 1.2 启动类加载器(BootStrap ClassLoader)...
  • Java中类加载采用的是双亲委托机制,为什么要这样处理?又是怎样实现的呢?接下来简单说说我的理解。
  • Java类加载ClassLoader--双亲委托模型

    千次阅读 2018-09-29 21:40:36
    双亲委托模型对于保证Java程序的稳定运作很重要,实现简单,实现双亲委托的代码都集中在java.lang.ClassLoader的loadClass()方法之中。方法实现如下: protected synchronized Class<?> loadClass(String name,...
  • classloader 类加载器和它的双亲委托机制 想必很多伙伴儿跟我一样,在接收知识的路上看见有些专业名词的表情如上↑↑↑↑↑ 我就想把这些看起来很难理解的名词给它简单通俗地理解了,有不到之处,还望多多指正,(❁´...
  • java ClassLoader类解析-双亲委托机制

    万次阅读 2015-10-27 14:43:05
    做Java开发,对于ClassLoader的机制是必须要熟悉的基础...本文将会从JDK默认的提供的ClassLoader,双亲委托模型,如何自定义ClassLoader以及Java中打破双亲委托机制的场景四个方面入手去讨论和总结一下。 JDK默认Cla
  • 双亲委托模式 注意:Java虚拟机并没有明确要求类加载器的加载机制一定要使用双亲委托模式,只是建议这样做,而在Tomcat中,当默认的类加载器接收到一个加载任务时,首先会由 它自动加载,当加载失败,才会将类委派...
  • 虚拟机为HotSpot 1.类加载器 类加载器一般只有3种(除开自己定义的)分别为: 1.Bootstrap ClassLoader:根加载器 2.Extension ClassLoader:扩展加载器 3.System ClassLoader:系统加载器 以下我们将对这3种加载器进行...
  • :指先委托父类加载器寻找目标类,在找不到的情况下 在 自己的路径中查找并载入目标类   实现一个我们自定义类加载器 ,只需要继承ClassLoader即可 1. public class MyClassLoader extends ...
  • 通俗易懂的双亲委托

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

    万次阅读 多人点赞 2019-12-24 12:00:33
    要想深入了解JVM,双亲委托模型是绕不过的,也是中高级开发面试时经常问到的。了解了它,你就会对JDBC、SPI、OSGi等的类加载机制有更深一层的理解。
  • 虚拟机类加载机制:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型...双亲委派模型: 某个特定的类加载器在接到加载类的请求时,首先将...
  • JVM ----双亲委托机制

    2021-04-28 17:56:20
    双亲委托机制解释:Java虚拟机对class文件采取的是按需加载的方式,也就是说需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委托模式即把请求交由...
  • 一、类加载器、 二、类加载的双亲委托机制、
  • 通过自定义类加载打破类加载双亲规则 @Override protected Class&amp;lt;?&amp;gt; loadClass(String name, boolean resolve) throws ClassNotFoundException { Class&amp;lt;?&amp;gt; clazz =...
  • 双亲委托类加载机制

    2021-09-09 00:29:15
    双亲委托类加载机制 加载机制 当前类加载器已经加载,则返回类,否则委托父加载器加载此类; 父加载器执行1的步骤知道Bootstrap ClassLoader 如果Bootstrap ClassLoader未加载,则由最开始的类加载器加载类 ...
  • 上面介绍ClassLoader的loadClass()源码的时候就可以看到类加载器在加载一个类的时候是会先委托给它的父加载器进行加载的,这其实就是双亲委托模型,在介绍双亲委托模型更具体的内容之前先来看下类加载器的分类。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 16,730
精华内容 6,692
关键字:

双亲委托