一.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)
对于基于栈的虚拟机来说,每个运行时的线程,都有一个独立的栈。栈中记录了方法调用的历史,每次方法调用,栈中便会
多一个栈桢。最顶部的栈桢称作当前栈桢,其代表着当前执行的方法。基于栈的虚拟机通过操作数栈进行所有操作。
文章图片
文章图片
基于寄存器的虚拟机中没有操作数栈,但有很多虚拟寄存器,其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上
就是一个数组,与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法的活动记录以帧为单位存在调用栈上;
与JVM版相比,可以发现Dalvik版程序的指令数明显减少了,数据移动次数也明显减少了。
文章图片
二.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. 虚拟机与类加载机制】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文件)可执行
文件(机器码)。
文章图片
三.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]]] 加载
文章图片
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;
}
文章图片
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;
文章图片
用到 BaseDexClassLoader DexPathList Gradle开发: 插桩+自动生成Dex;
- 获取到当前应用的PathclassLoader
- 反射获取大DexPathList属性对象pathList;
- 反射修改pathList的dexElements;
- 把补丁包path.dex转化为Element[] (patch)
- 获取pathList的dexElements属性(old)
- patch + old 合并,并反射赋值给pathList的dexElements;
hotfix(path)
dex(dexPath)->反射执行 makePathElements(dex文件:补丁包) 补丁包对应的element数组;
class->jar->dex 这个av老师讲过的,用dx工具,可以生成dex文件
PathClassLoader-->art 代码(A)
Tinker: 自定义ClassLoader 反射替换系统创建的PathClassLoader
推荐阅读
- Java|【面试资料】 Java中高级核心面试知识解析
- 后端|MySQL 灵魂 16 问,你能撑到第几问()
- 后端|万字总结(分布式系统的38个知识点)
- java|手把手教你springboot集成mybatis
- java|SpringBoot设置接口超时时间的方法
- java|将String类型转换成Map数据类型
- 面试|MySQL 免安装版的下载与配置教程
- 面试|MySQL 入门(Case 语句很好用)
- 面试|MySQL 出现 The table is full 的解决方法