精华内容
下载资源
问答
  • JAVA类加载,双亲委托机制
    2020-07-12 15:22:53

    初始化顺序 先后顺序是:静态成员变量、成员变量、构造方法。

    更多相关内容
  • 一、前言无论是做Java开发,还是Android开发,ClassLoader是少不了打交道的。...Android本身有着和Java同源的血统,因此,ClassLoader的核心功能机制是不会变的:IO读取Class文件,解析,分配栈、堆,加载到永久代(P...

    一、前言

    无论是做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下发就失败了。

    展开全文
  • 双亲委托机制

    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这个类了

    展开全文
  • 目录前言DexART与Dalvikdexopt与dexaotAndroid N(7.0)混合编译ClassLoader介绍双亲委托机制双亲委托机制原理使用双亲委托机制目的1.安全。防止核心API库被篡改。2.避免重复加载。当一个类被父类加载器加载过的时候,...

    目录

    前言

    Dex

    ART与Dalvik

    dexopt与dexaot

    Android N(7.0)混合编译

    ClassLoader介绍

    双亲委托机制

    双亲委托机制原理

    使用双亲委托机制目的

    1.安全。防止核心API库被篡改。

    2.避免重复加载。当一个类被父类加载器加载过的时候,就没必要再用子类加载器加载一遍了。

    ClassLoader.loadClass()关键代码

    参考

    前言

    一个Java程序,会通过javac编译成class文件,然后通过虚拟机加载(ClassLoader)到方法区,执行引擎会执行这些字节码,并翻译成操作系统底层相关函数。这是JVM运行java代码的整体流程。

    由于Java中的ClassLoader类加载机制和Android中是不同的,本文将介绍Android虚拟机的类加载机制

    Dex

    了解JVM的老铁都知道,JVM运行的是Class字节码。

    由于Class字节码文件IO操作比较多、查找类效率低、基于栈的加载模式加载速度慢以及内存占用较大的缺点,并不适用于移动端。所以DVM(Dalvik VM)设计了一种压缩文件的格式Dex(Dalvik Executable Format)

    Dex文件是由多个.class文件处理压缩后的产物,Dex最终可以在Android运行时环境执行。

    ART与Dalvik

    1.DVM(Dalvik VM)是实现了JVM虚拟机规范的一个虚拟机,默认使用CMS垃圾回收器,与JVM不同的是DVM运行Dex文件;Android应用程序运行在虚拟机,一个进程对应一个单独的虚拟机实例,所以一个Android应用至少有对应一个单独的虚拟机实例

    2.ART(Android Runtime)是在Anndroid 4.4版本引入的一个开发者选项,也是Android5.0及以上版本默认的Android运行时。ART虚拟机执行的是本地机器码(预编译得到机器码)。Android的运行时从Dalvik换成ART并不需要开发者将应用编译成机器码,APK任然是一个包含dex字节码的文件(ART和Dalvik都是运行Dex字节码的兼容运行时,因此针对Dalvik开发的应用也能运行在ART虚拟机中。)

    3.ART与Dalvik的差别是二者执行的指令集不同,ART的指令集是基于寄存器的,Dalvik的指令集是基于堆栈的。

    dexopt与dexaot

    1.dexopt:在Dalvik加载一个dex文件的时候,会对dex文件进行验证和优化,验证和优化后变成odex(Optimized dex [/'ɒptɪmaɪzd/])文件,odex与dex很像,只是使用了一些优化操作码;

    2.dexaot:ART预先编译机制,在应用安装时对dex文件执行AOT(Ahead-Of-Time)提前编译操作,编译为OAT(OAT文件本质上是一个ELF文件,它将OAT文件格式内嵌在ELF文件里)可执行文件(机器码)

    Android N(7.0)混合编译

    c737047a625c5d3b4255a54cd95b791b.png

    有没有发现Android应用从7.0版本开始在手机配置差别不大的情况下安装应用变得比之前的Android版本下快了。

    这就得益于Android N的混合编译

    ART使用AOT(Ahead-Of-Time)编译,在应用安装在应用安装时对dex文件提前编译。使得安装变得缓慢,从Android N开始混合使用AOT编译+解释+JIT

    1.初次安装应用时不进行AOT编译,运行过程中解释执行,对经常执行的方法进行JIT,经过JIT编译的方法会记录到Profile配置文件中

    2.当处于设备闲置或充电或有四个小时间隔时,编译守护进程会运行,根据Profile文件对常用代码(“热代码”)进行AOT(All Of the Time compilation:全时段编译)编译,生成base.art文件(称为 app_image 类对象映像)。这个art文件会在apk启动时会加入到PathClassloader的ClassTable中,系统在查找类的时候会先查找base.art中的。

    Android N混合编译主要解决的问题:

    1.应用安装时间过长;在N之前,应用在安装时需要对所有ClassN.dex做AOT机器码编译,类似微信这种比较大型的APP可能会耗时数分钟。但是往往我们只会使用一个应用20%的功能,剩下的80%我们付出了时间成本,却没带来太大的收益。

    2.降低占ROM空间;同样全量编译AOT机器码,12M的dex编译结果往往可以达到50M之多。只编译用户用到或常用的20%功能,这对于存储空间不足的设备尤其重要。

    3.提升系统与应用性能;减少了全量编译,降低了系统的耗电。在boot.art的基础上,每个应用增加了base.art, 通过预加载与缓存提升应用性能。

    4.快速的系统升级;以往厂商ota时,需要对安装的所有应用做全量的AOT编译,这耗时非常久。事实上,同样只有20%的应用是我们经常使用的,给不常用的应用,不常用的功能付出的这些成本是不值得的。

    ClassLoader介绍

    Android9.0下ClassLoader的UML类图

    ab655d37d5dfbbda66a39bdf1ee7e450.png

    一个Java程序,会通过javac编译成一个或多个class文件,运行的时候,需要将class文件加载到虚拟机中使用,负责加载这些文件的就是Java的类加载机制。

    ClassLoader的作用就是加载class文件到方法区,提供给程序运行的时候使用。

    ClassLoader是一个抽象类。具体的实现类有三个

    1.BootClassLoader

    BootClassLoader用于加载Android Framework层的class文件

    2.PathClassLoader

    PathClassLoader是Android应用程序的类加载器(加载已安装应用的dex),可以加载指定的dex和在java、zip、apk中的dex

    在Dalvik虚拟机上PathClassLoader只能加载已安装的apk的dex,Android5.0开始使用的ART虚拟机,PathClassLoader也可以加载指定的dex和在java、zip、apk中的dex

    3.DexClassLoader

    DexClassLoader用于加载指定的dex和在java、zip、apk中的dex

    双亲委托机制

    双亲委托机制原理

    某个类加载器在加载类的时候,首先委托给父加载器(ClassLoader parent)加载,其父加载器再委托给它的父加载器(如果有的话),以此类推。

    如果父加载器可以完成加载类的任务的话,就成功返回

    如果父加载器不能完成加载类的任务或者没有父加载器的话,自己去加载类。

    使用双亲委托机制目的

    1.安全。防止核心API库被篡改。

    即使用户自定义的类与java核心api中的类名相同,因为双亲委托机制首先会使用父类加载器加载,由于PathClassLoader是Android应用程序的类加载器,应用开发者自己写的代码由PathClassLoader加载,PathClassLoader的父加载器是BootClassLoader,核心API类的Class已经被BootClassLoader加载过了,所以用户自定义的类不会加载。比如开发者自定义了一个String类,如果没有双亲委托机制,就会篡改String类

    de16cf26a0722c2be4786d02f88f2c6a.png

    为什么PathClassLoader的父加载器是BootClassLoader?

    我的这篇文章有介绍

    这里需要注意一下,父加载器不是父类加载器,两者不是继承和被继承的关系,而是parent是创建ClassLoader的一个参数

    2.避免重复加载。当一个类被父类加载器加载过的时候,就没必要再用子类加载器加载一遍了。

    ClassLoader.loadClass()关键代码

    protected Class> loadClass(String name, boolean resolve)

    throws ClassNotFoundException

    {

    // First, check if the class has already been loaded 检查class是否已经被加载

    Class> c = findLoadedClass(name);

    if (c == null) {

    try {

    //父类加载器不为null就用父类加载器加载

    if (parent != null) {

    c = parent.loadClass(name, false);

    } else {

    //父类加载器为null就用BootClassLoader加载

    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;

    }

    参考

    Android N混合编译与对热补丁影响解析

    展开全文
  • title: 类加载机制(五):自定义类加载器与深入双亲委托机制 date: 2019-03-17 08:24:05 categories: Java虚拟机 tags: 类加载机制 自定义类加载器 引言 我们知道类加载器共分为两大类型,Java虚拟机自带的类...
  • java 双亲委托机制

    2020-11-10 16:29:33
    JVM设计者把类加载阶段中的“通过’类全名’来获取定义此类的二进制字节流”这个动作放到Java...2.双亲委派模型 从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该
  • 相信我们平时在工作中都遇到过 ClassNotFound 异常,这个异常表示 JVM 在尝试加载某个类的时候...Tomcat 正是通过 Context 组件来加载管理 Web 应用的,所以今天我会详细分析 Tomcat 的类加载机制。但在这之前,我..
  • classloader 类加载器和它的双亲委托机制 想必很多伙伴儿跟我一样,在接收知识的路上看见有些专业名词的表情如上↑↑↑↑↑ 我就想把这些看起来很难理解的名词给它简单通俗地理解了,有不到之处,还望多多指正,(❁´...
  • 写在前面 ...双亲委托机制3.总结 1.类加载器 1.1 类加载器作用: 类加载器负责从文件或者网络中加载Class信息,加载的类信息存放于方法区的内存空间。 1.2 启动类加载器(BootStrap ClassLoader)...
  • Java中类加载采用的是双亲委托机制,为什么要这样处理?又是怎样实现的呢?接下来简单说说我的理解。
  • java ClassLoader类解析-双亲委托机制

    万次阅读 2015-10-27 14:43:05
    做Java开发,对于ClassLoader的机制是必须要熟悉的基础...本文将会从JDK默认的提供的ClassLoader,双亲委托模型,如何自定义ClassLoader以及Java中打破双亲委托机制的场景四个方面入手去讨论和总结一下。 JDK默认Cla
  • 使用SPI server provider模式的JDBC JAXP都是破坏了双亲委托模式的,在核心类库rt.jar的加载过程中需要加载第三方厂商的类,直接指定使用线程上下文类加载器也就是应用程序类加载器来加载这些类;Tomcat中的web 容器...
  • 双亲委托机制 某个特定的类加载器接收到类加载的请求时,会将加载任务委托给自己的父类,直到最高级父类引导类加载器(Bootstrap ClassLoader),如果父类能够加载就加载,不能加载则返回到子类进行加载。...
  • 如下图所示: 双亲委托模型 从1.2版本开始,Java引入了双亲委托模型,从而更好的保证Java平台的安全。 在此模型下,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类...
  • 类加载过程回顾下类加载的源码,如下图:上一篇文章中自定义的类加载器直接重写了loadClass,打乱了类加载的双亲委托机制,今天需要在不破坏这个机制下自定义!从源码中可以看出如果在parent最终不能加载的情况下,...
  • 1、引言 看了王森老师的《java深度历险》(我的资料中有PDF版下载),在讲到加载器委托模型时(p69),按照书上的指示,做实验,但是不成功,非常奇怪。包括我查阅了很多资料,发现都不能达到和书中讲的效果。2、...
  • JVM学习笔记(2)之类加载器双亲委托机制实例 1、什么是类加载器的双亲委托机制 类的加载器分为三种,也可以自定义,分别为Bootstrap Class Loader(启动加载器)、Extensions Class Loader(扩展加载器)、Application ...
  • 深入理解双亲委托机制

    万次阅读 2018-07-31 16:59:47
    JVM设计者把类加载阶段中的“通过'类全名'来获取定义此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”...2.双亲委派模...
  • Java之双亲委托机制

    千次阅读 2019-05-22 00:16:04
    双亲委派机制的工作流程: 1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。 每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等...
  • 类加载用双亲委托机制: 当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。 为什么类加载用双亲委托机制: 父类...
  • 自己看了一上午的代码,终于弄懂了这个双亲委托机制。 知识误区 在没看源码之前,一直觉得JVM是通过(包名+类名)来唯一标识一个类的。看完源码才知道,原来不是的,JVM是通过(ClassLoader+包名+类名)来唯一标识...
  • 通过自定义类加载打破类加载双亲规则 @Override protected Class&amp;lt;?&amp;gt; loadClass(String name, boolean resolve) throws ClassNotFoundException { Class&amp;lt;?&amp;gt; clazz =...
  • Java 类加载体系之 ClassLoader 双亲委托机制 java 是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是: 类加载体系 .class文件检验器 内置于java虚拟机的安全...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 14,133
精华内容 5,653
关键字:

双亲委托机制

友情链接: 分类.rar