Android 虚拟机与类加载机制


文章目录

  • Android 虚拟机与类加载机制
  • 一. Android 虚拟机版本
  • 1.1 概述
  • 1.2 基于栈的虚拟机
  • 1.2.1 字节码指令
  • 1.2.2 执行过程
  • 1.3 基于寄存器的虚拟机
  • 1.3.1 执行过程
  • 1.3.2 和基于栈的虚拟机的区别
  • 1.4 ART 和Dalvik的区别
  • 1.4.1 ARTx虚拟机执行的本地机器码是从哪里来的
  • 1.4.2 安装时进行预编译带来的问题
  • 1.4.2 Android N的运作方式
  • 二. ClassLoader
  • 2.1 常见的类加载器
  • 2.2 类加载流程
  • 2.3 为什么不直接在dexElements中查找
  • 三. 热修复原理
  • 3.1 热修复原理分析
  • 3.2 热修复代码实现


一. Android 虚拟机版本

1.1 概述

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

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

android虚拟机线程 安卓虚拟机运行机制_操作数

1.2 基于栈的虚拟机

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

android虚拟机线程 安卓虚拟机运行机制_操作数_02

1.2.1 字节码指令

android虚拟机线程 安卓虚拟机运行机制_寄存器_03

1.2.2 执行过程

android虚拟机线程 安卓虚拟机运行机制_寄存器_04

  1. 首先将int类型常量压入操作数栈
  2. 然后将操作数栈顶int类型的值存入到局部变量表0的位置
  3. 将int类型的常量压入操作数栈
  4. 将操作数栈定的int类型的值存入局部变量表中1的位置
  5. 将局部变量表0的变量的值压入操作数栈
  6. 将局部便令表1的变量的值压入操作数栈
  7. 执行相加操作
  8. 将相加的结果存入局部变量表2的位置
  9. 然后返回。
1.3 基于寄存器的虚拟机

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

1.3.1 执行过程

android虚拟机线程 安卓虚拟机运行机制_操作数_05

  1. 将操作数存放到虚拟寄存器V0的位置
  2. 将操作数存放到虚拟寄存器V1的位置
  3. 执行相加操作,将结果存放到虚拟寄存器V2的位置上
  4. 返回
1.3.2 和基于栈的虚拟机的区别

android虚拟机线程 安卓虚拟机运行机制_寄存器_06

很明显,我们可以发现,基于寄存器的虚拟机对同样的操作执行的执行更少了,数据移动的次数也更少了。

1.4 ART 和Dalvik的区别

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

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

1.4.1 ARTx虚拟机执行的本地机器码是从哪里来的

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

android虚拟机线程 安卓虚拟机运行机制_寄存器_07

1.4.2 安装时进行预编译带来的问题

安装变的很慢,因为要将dex字节码编译成本地机器码。

1.4.2 Android N的运作方式

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

  1. 最初安装应用的时候不进行任何AOT编译(安装又变快了),运行过程中解释执行,对经常执行的代码进行JIT,经过JIT编译的方法将会记录到Profile配置文件中。
  2. 当设备闲置和充电时,编译守护进行会运行,根绝Profile文件对常用代码进行AOT编译,待下次运行时直接使用。

android虚拟机线程 安卓虚拟机运行机制_寄存器_08

二. ClassLoader

ClassLoader:类加载器,专门负责加载类的对象。ClassLoader类是一个抽象类。如果给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的 “类文件”。

每个Class对象都包含一个对定义它的ClassLoader的引用。

数组类的Class对象不是由类加载器创建的,而是由Java运行时根据需要自动创建。数组类的类加载器由Class.getClassLoader()返回,该加载器与元素类型的类加载器是相同的;如果该元素类型是基本类型,则该数组类没有类加载器。

应用程序需要实现ClassLoader的子类,以扩展Java虚拟机动态加载类的方式。

2.1 常见的类加载器

android虚拟机线程 安卓虚拟机运行机制_寄存器_09

ClassLoader 是一个抽象类,有两个子类,分别是BootClassLoader,BaseDexClassLoader,其中BootClassLoader负责加载Android Framework层的class文件,

在系统启动的时候便会调用BootClassLoader。

PathClassLoader 是我们应用程序的类加载器,只能加载系统中已经安装过的apk,DexClassLoader 主要用于加载未安装的jar /apk/dex

2.2 类加载流程

此处以PathClassLoader 为例来说明类的加载过程

继承自BaseDexClassLoader

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

android虚拟机线程 安卓虚拟机运行机制_android虚拟机线程_10

上图就是类加载的流程,大概就是说,在创建PathClassLoader实例的时候会根据传入的dex文件的地址去找dex文件,然后把文件放入到一个dexElements的数组中

调用loadClass方法的时候会先调用findLoadedClass在缓存中查找,如果有直接返回,如果没有则从父类加载器中查找,一层一层往上找,如果父类中有则返回,如果没有,那么调用自己的findClass在dexElements中查找,如果有就返回,没有报异常。

2.3 为什么不直接在dexElements中查找
  1. 如果缓存中有则直接返回,速度快
  2. 安全,可以防止用户修改核心类,通过上面的加载流程可以发现,首先从缓存中找,找不到从父类加载器中找,因为核心类是在系统启动时候就加载了的,所以一般可以在缓存中找到,再只就是优先从父类加载器中找,这样就可以避免优先加载用户自定义的同限定名的类了。

三. 热修复原理

3.1 热修复原理分析

从上面的类加载机制中可以看到,用户自己定义的类一般是在dexElements中找的,我们看一下怎么查找的:

for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

for 循环匹配,那么我们在dexElements的数组最前面加入我们要修复的dex文件就可以达到修复的问题,如下图:

正常的文件数组,要加载的为dex1:

android虚拟机线程 安卓虚拟机运行机制_操作数_11

首先遍历dex0 ,然后dex1,找到之后返回

假如说dex1这时候有bug了,我们需要修复,那么我们可以把dex1加到数组最前面,这样在遍历的时候首先找到的就是我们修复好的dex,这样就达到了热修复的目的。

如下图:

android虚拟机线程 安卓虚拟机运行机制_寄存器_12

就这样,我们把新的dex1插入到数组的最前面,在查找的时候便会首先返回的是新的dex1。热修复的思路就是这样的。

3.2 热修复代码实现
有空再完善