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

对于基于栈的虚拟机来说,每个运行时的线程,都有一个独立的栈。栈中记录了方法调用的历史,每次方法调用,栈中便会
多一个栈桢。最顶部的栈桢称作当前栈桢,其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。
JAVA|12. 虚拟机与类加载机制
文章图片

JAVA|12. 虚拟机与类加载机制
文章图片


基于寄存器的虚拟机中没有操作数栈,但有很多虚拟寄存器,其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上
就是一个数组,与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法的活动记录以帧为单位存在调用栈上;
与JVM版相比,可以发现Dalvik版程序的指令数明显减少了,数据移动次数也明显减少了。
JAVA|12. 虚拟机与类加载机制
文章图片


二.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编译,待下次运行时直接使用;
JAVA|12. 虚拟机与类加载机制
文章图片

【JAVA|12. 虚拟机与类加载机制】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文件)可执行
文件(机器码)。

JAVA|12. 虚拟机与类加载机制
文章图片

三.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]]] 加载


JAVA|12. 虚拟机与类加载机制
文章图片


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



JAVA|12. 虚拟机与类加载机制
文章图片

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;
JAVA|12. 虚拟机与类加载机制
文章图片

用到 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

    推荐阅读