12. 虚拟机与类加载机制

一.JVM 与 Dalvik

Android应用程序运行在Dalvik/ART虚拟机,且每个应用程序对应一个单独的Dalvik虚拟机实例,Dalvik虚拟机实则也算一个规范

的java虚拟机默认使用CMS垃圾回收器, 但是与JVM运行 Class 字节码不同,DVM执行dex文件,它是很多.class文件处理压缩后的

产物,最终可在 Android 运行时环境执行。

Dalvik虚拟机与Java虚拟机共享有差不多的特性,差别在于两者的执行指令集不一样,前者的指令集是基本寄存器,

后者的指令集是基于堆栈; .class file(one file one class), .dex file(one file ,many classes)

对于基于栈的虚拟机来说,每个运行时的线程,都有一个独立的栈。栈中记录了方法调用的历史,每次方法调用,栈中便会

多一个栈桢。最顶部的栈桢称作当前栈桢,其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。

12. 虚拟机与类加载机制_第1张图片

12. 虚拟机与类加载机制_第2张图片

基于寄存器的虚拟机中没有操作数栈,但有很多虚拟寄存器,其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上

就是一个数组,与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法的活动记录以帧为单位存在调用栈上;

与JVM版相比,可以发现Dalvik版程序的指令数明显减少了,数据移动次数也明显减少了。

12. 虚拟机与类加载机制_第3张图片

二.ART 和 Dalvik

Dalvik 虚拟机执行的是dex字节码,解释执行,从Android 2.2版本开始, 支持JIT 即时编译(Just In Time)在程序运行

的过程中进行选择热点代码(经常执行的代码) 进行编译或优化;

而ART(Android Runtime)是在Android 4.4中引入的一个开发者选项,也是Android5.0及更高版本默认Android运行时,

ART虚拟机执行的是本地机器码,Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译

成目标机器码,APK仍然是一个包含dex字节码的文件;

dex2aot(将dex编译为本地机器码)

Dalvik下应用在安装的过程中,会执行一次优化,将dex字节码进行优化生成odex文件,而ART下降应用的dex字节码翻译成

本地机器码的最恰当AOT时机也就发生在应用安装的时候,ART引用预编译机制(AOT,Ahead of Time),在安装时,ART使用

设备自带的dex2oat工具来编译应用,dex中的字节码将被编译成本地机器码;(oat 是elf文件 so)

在安装时拿到apk中的classes.dex文件去到dalvik运行,

Android N 的运作方式

ART使用预先(AOT)编译,并从Android N混合使用AOT编译,解释和JIT

dex2oat 为什么不在打包的时候打进去

1.机器码体积大 2.兼容问题 x86 arm 都可以;

1)最初安装应用时不进行任何AOT编译(安装又快了),运行过程中解释执行,对经常执行的方法进行JIT,

经过JIT编译的方法将会记录到Profile配置文件中;

2)当设备闲置和充电时,编译守护进程会运行,根据Profile文件对常用代码进行AOT编译,待下次运行时直接使用;

12. 虚拟机与类加载机制_第4张图片

3)ART与Dalvik的区别

*1.Dalvik环境中,应用每次运行时,字节码都需要通过即时编译器(Just In Time,JIT)转换为机器码,

ART环境中,应用会在安装的时候,就将字节码 预编译(Ahead of Time, AOT)成机器码,使其成为真正的本地应用;

*2.ART占用的空间比Dalvik大,就是用空间换时间

*3.ART不用每次运行时都重复编译,减少CPU的使用频率,降低了能耗;

4) dexopt与dexaot

dexopt

Dalvik中虚拟机在加载一个dex文件时,对 dex 文件 进行 验证 和 优化的操作,其对 dex 文件的优化结果

变成了 odex(Optimized dex) 文件,这个文件和 dex 文件很像,只是使用了一些优化操作码。

dex2oat

ART 预先编译机制,在安装时对 dex 文件执行AOT 提前编译操作,编译为OAT(实际上是ELF文件)可执行

文件(机器码)。

12. 虚拟机与类加载机制_第5张图片

三.ClassLoader

1>ClassLoader简介

任何一个Java程序都由一个或多个class文件组成,在程序运行时,需要将class文件加载到JVM中才可以使用,负责加载这些class文件的就是Java的类加载机制,ClassLoader的作用简单来说就是加载class文件,提供给程序运行时使用,每个Class对象的内部都有一个ClassLoader字段来表示自己是由哪个ClassLoader加载的

class Class{
    ...
    private transient ClassLoader classLoader;
    ...
}
Log.e(TAG, "Activity.class 由:" + Activity.class.getClassLoader() +" 加载"); 
Log.e(TAG, "MainActivity.class 由:" + getClassLoader() +" 加载"); 
//输出: 
Activity.class 由:java.lang.BootClassLoader@d3052a9 加载 
MainActivity.class 由:dalvik.system.PathClassLoader[DexPathList[[zip file 
"/data/app/com.enjoy.enjoyfix-1/base.apk"],nativeLibraryDirectories= 
[/data/app/com.enjoy.enjoyfix-1/lib/x86, /system/lib, /vendor/lib]]] 加载

12. 虚拟机与类加载机制_第6张图片

zygote:fork 双进程守护-->ActivityThread-->#main PathClassLoader(比如Application, )

2> 双亲委托机制

可以看到创建 ClassLoader 需要接收一个 ClassLoader parent 参数。这个 parent 的目的就在于实现类加载

的双亲委托。即:

某个类加载器在加载类时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,

就成功返回;只有父类加载器无法完成此加载任务或没有父类加载器时,才自己去加载;

因此创建ClassLoader:new PathClassLoader("/sdcard/xx.dex", getClassLoader());并不仅仅只加载xx.dex中的class

1) 避免重复加载,当父加载器已经加载了该类的时候,就没有必要子ClassLoader再加载一次

2) 安全性考虑,防止核心API库被随意篡改; 代码如下

protected Class<?> loadClass(String name, boolean resolve) throw ClassNotFoundException{
      //检查class是否有被加载
      Class c=findLoadClass(name);
      if(c==null){
        long t0=System.nanoTime();
        try{
            if(parent!=null){
                //如果parent不为null,则调用parent的loadClass进行加载
                c=parent.loadClass(name,false);
            }else{
                //parent为null,则调用BootClassLoader进行加载
                c=findBootstrapClassOrNull(name);
            }
        }catch(ClassNotFoundException e){}
         if(c==null){
           //若都找不到就自己查找
           long t1=System.nanoTime();
           c=findClass(name);
         }
      }
       return c;
    }

12. 虚拟机与类加载机制_第7张图片

apk->ClassLoader(DexPathList(dexElements(dexFile1 dexFile2) ->dexFile.loadClassBinaryName))

 public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath,
                              ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
    }
    //实现就是从pathList中查找class
    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        List suppressedExceptions = new ArrayList();
        //查找指定的
        class Class c =pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" +
                    name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }




    public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath,
                       File optimizedDirectory) {
        //.........
        // splitDexPath 实现为返回 List.add(dexPath)
        //splitDexPath:拆组; /a/a.dex:/b.dex
        // makeDexElements 会去 List.add(dexPath) 中使用DexFile加载dex文件返回 Element数组
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                suppressedExceptions, definingContext);
        //.........
    }


    public Class findClass(String name, List suppressed) {
        //从element中获得代表Dex的 DexFile
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;
            if (dex != null) {
                // 查找class
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

四.热修复

PathClassLoader中存在一个Element数组,Element类中存在一个dexFile成员表示dex文件,即:APK中有x个dex,

则Element数组就有x个元素,在pathClassLoader中的Element数组为:[path.dex,class.dex,class2.dex],

如果存在key.class位于patch.dex与classes2.dex中都存在一份,当进行类查找时,循环获得dexElements中的DexFile,

查找到Key.class则立即返回,不再管后续element中的DexFile是否能加载到Key,class了;

因此实际上,一种热修复实现可以将BUG的class单独制作一份fix.dex文件(补丁包),然后在程序启动时,从服务器下载fix.dex

保存到某个路径,在通过fix.dex的文件路径,用其创建Element对象,将这个Element对象插入到我们程序的类加载器

PathClassLoader的pathList中的dexElements数组头部,这样在加载出现Bug的class时,会优先加载fix.dex中的修复类,

从而解决BUG;

12. 虚拟机与类加载机制_第8张图片

用到 BaseDexClassLoader DexPathList Gradle开发: 插桩+自动生成Dex;

  • 获取到当前应用的PathclassLoader

  • 反射获取大DexPathList属性对象pathList;

  • 反射修改pathList的dexElements;

  1. 把补丁包path.dex转化为Element[] (patch)

  2. 获取pathList的dexElements属性(old)

  3. patch + old 合并,并反射赋值给pathList的dexElements;

创建一个新数组,将获取的宿主和插件的数组拷贝到新数组中,将宿主放在前面,通过反射将新数组赋值给宿主生成新的dexElement

hotfix(path)

dex(dexPath)->反射执行 makePathElements(dex文件:补丁包) 补丁包对应的element数组;

class->jar->dex 这个av老师讲过的,用dx工具,可以生成dex文件

PathClassLoader-->art 代码(A)

Tinker: 自定义ClassLoader 反射替换系统创建的PathClassLoader

你可能感兴趣的:(JAVA,jvm,java,android)