当我们写了一个方法,那么这个方法是如何被执行的呢?
public int add(){
int a = 10;
int b = 20;
return a + b;
}
其实方法的本质就是arm指令,然后JVM的执行引擎会执行arm指令
add方法是java代码,java代码编译成class文件,还需要一步转换为dex文件,才能被Android虚拟机执行,arm指令是存在于dex文件中的,也就是说,我们可以从dex文件中取出arm指令,查看一个方式是如何被执行的
JVM内存调度机制
1 arm指令集
dx --dex --verbose --dump-to=dex_method.txt --dump-method=Method.add --verbose-dump Method.class
Android中可以通过dx命令将class文件转换为dex文件,dx.bat位于Android SDK中的build-tools文件夹下,那么可以通过dx命令将class文件翻译成arm指令集
我看可以看一下,打印输出的arm指令集,Android虚拟机执行某个方法的时候,执行的就是这个指令集,这种指令集在加载的时候,存在JVM虚拟机的方法区中
执行的时候,JVM的执行引擎会将arm指令从方法区中拿出来,放到虚拟机栈中执行(栈帧的概念,每一个方法对应的dx指令集就是一个栈帧,每一次方法调用都有栈帧入栈和出栈)
关注点回到指令集上,在每一行指令前有一个数字,代表程序计数器记录的行号,精简之后的指令集(只保留每个行号的最后一个)
Method.add:()I:
regs: 0002; ins: 0001; outs: 0000
0000: const/16 v0, #int 30 // #001e
0002: return v0
0003: code-address
debug info
line_start: 4
parameters_size: 0000
0000: prologue end
0000: line 4
0000: line 6
end sequence
source file: "Method.java"
另外还有一种方式获取字节码,是通过javap获取,这种跟dex指令有啥区别呢?其实都是字节码,但是javap获取的字节码是JVM执行的字节码,Android虚拟机是Dalvik或者Art虚拟机,执行的是dx指令集
public int add();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_2
7: iload_1
8: idiv
9: ireturn
LineNumberTable:
line 4: 0
line 5: 3
line 6: 6
这两者有什么区别呢?我们看同是执行 10 + 20 ,JVM是先创建一个10变量,然后再创建20 ,最后将两个相加然后返回;但是dx指令是直接计算好了,然后创建v0 = 30,直接返回,所以:Android编译器在编译的过程中会做优化,提高执行的效率
当一个class类加载进来之后,class类中有方法、成员变量等,这些类的信息加载的时候是放在方法区,当Java层调用某个方法时,执行引擎从方法区拿出dx指令集,作为栈帧拷贝到虚拟机栈(高速缓存区),CPU去读取每行指令,程序计数器+1,等到方法执行完毕,栈帧出栈。
2 AndFix热修复原理
之前我们介绍过阿里的AndFix或者Sophix是通过hook native层修改字节码指令完成,之前我们介绍的arm指令集,就是实现热修复的基础。
Method.add:()I:
regs: 0002; ins: 0001; outs: 0000
0000: const/16 v0, #int 30 // #001e
0002: return v0
0003: code-address
debug info
line_start: 4
parameters_size: 0000
0000: prologue end
0000: line 4
0000: line 6
end sequence
source file: "Method.java"
public class Method {
public int add(){
int a = 10;
int b = 20;
return b / a;
}
}
//调用
Method method = new Method