android虚拟机、类加载机制和热修复

811 阅读4分钟

一、JVM和Dalvik的区别

Android应用程序运行在Dalvik/ART虚拟机,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。Dalvik虚拟机实则也算是一个Java虚拟机,只不过它执行的不是class文件,而是dex文件。

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

image.png

二、基于栈的虚拟机()

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

image.png

三、基于寄存器的虚拟机

基于寄存器的虚拟机中没有操作数栈,但是有很多虚拟寄存器。其实和操作数栈相同,这些寄存器也存放在运行时栈中,本质上就是一个数组。与JVM相似,在Dalvik VM中每个线程都有自己的PC和调用栈,方法调用的活动记录以帧为单位保存在调用栈上。

image.png

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

四、Dalvik和ART

Dalvik

Dalvik虚拟机执行的是dex字节码,解释执行。从Android 2.2版本开始,支持JIT即时编译(Just In Time) 在程序运行的过程中进行选择热点代码(经常执行的代码)进行编译或者优化。

ART

ART(Android Runtime) 是在 Android 4.4 中引入的一个开发者选项,也是 Android 5.0 及更高版本的默认 Android 运行时。ART虚拟机执行的是本地机器码。Android的运行时从Dalvik虚拟机替换成ART虚拟机,并不要求开发者将自己的应用直接编译成目标机器码,APK仍然是一个包含dex字节码的文件。

dex2aot

Dalvik下应用在安装的过程,会执行一次优化,将dex字节码进行优化生成odex文件。而Art下将应用的dex字节码翻译成本地机器码的最恰当AOT时机也就发生在应用安装的时候。ART 引入了预先编译机制(Ahead Of Time),在安装时,ART 使用设备自带的 dex2oat 工具来编译应用,dex中的字节码将被编译成本地机器码。

android7.0以后采用混编

因为art在安装应用的时候需要把dex中的字节码编译成机器码。这样会使应用安装的时候变慢。于是有了混编。

1、最初安装应用时不进行任何 AOT 编译(安装又快了),运行过程中解释执行,对经常执行的方法进行JIT,经过 JIT 编译的方法将会记录到Profile配置文件中。

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

image.png

五、android下的classloader

  • BootClassLoader:加载android framework下的class的。
  • PathClassLoader:程序的classLoader parent->BootClassLoader

类加载代码

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
        // First, check if the class has already been loaded
        //现在内存中找是否加载过
        Class<?> c = findLoadedClass(name);
        //再双拼委派模型去找。
        if (c == null) {
            try {
                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.
                c = findClass(name);
            }
        }
        return c;
}

双亲委托机制

作用

  • 避免类的重复加载。父类加载过了,子类就不用再加载了。
  • 安全性:防止核心代码被篡改

类加载流程

  1. pathClassLoader调用loadclass方法,实现再父类BaseClassLoader里。

  2. BaseClassLoader调用findClass方法。调用的DexPathList的findClass()

  3. DexPathList 里有dexElements的数组。每个Element元素里都有一个DexFile.

  4. 再通过loadClassBinaryName来查找对应名字的class。

image.png

六、热修复

  1. 获取应用的 pathClassLoader
  2. 通过反射拿到pathClassLoader里的 PathList变量(DexPathlist)。
  3. 把自己的补丁包转换成element数组,通过调用DexPathList的makePathElements方法生成数组。
  4. 把两个elements数组合并在一起。通过反射设置回去。

生成dex文件的方法。

  1. 设置环境变量。dx.bata的。

  2. 编译工程,进入classes文件夹下。

  3. 执行指令:dx --dex --output=path.dex com/imi/myapplication/activity/ShowActivity.class

  4. Android 10.0以上需要在xml的application上配置android:requestLegacyExternalStorage="true"

环境变量的位置:

image.png

image.png