14的虚拟机可以用在15上面吗_Android虚拟机之 ART

我们都知道,Android 是用 Java 开发的,那么为什么 Android 不直接用 Java 的 JVM 虚拟机呢?

20c3c213fcd657e8f5de69ef82781165.png 这是因为 JVM 把 Java 编译成的 class 字节码里面具有很多冗余信息,而且每次读取字节码之前都要进行 class 文件的读取,这样就会多次读写,而使用 Android 虚拟机则会把所有 class 文件打包成一个 dex 文件,这个 dex 文件就是压缩后去除冗余信息之后的字节码,Android 读取字节码只要读取整个 dex 文件即可,这样加速了运行速度。而且 JVM 是基于栈的虚拟机,而 Android 是基于寄存器的虚拟机

上面提到栈和寄存器虚拟机,那么它俩有什么区别呢?

20c3c213fcd657e8f5de69ef82781165.png  基于栈虚拟机读取指令是在内存上工作的,而寄存器读取指令则是在高速缓存区(寄存器)上工作,CPU 直接操作,执行更快。栈虚拟机更不依赖于硬件,而寄存器虚拟机会根据硬件来进行优化,所以不适合多平台移植。并且如果做同一个操作,栈的指令比寄存器的指令更多。总的来说就是以空间换时间

知道 Android 为什么要使用自己的虚拟机后,那么为什么 Android 会在 4.4 版本之后抛弃 Dalvik 虚拟机,而反而使用 ART 虚拟机了呢,它两又有什么区别呢?

20c3c213fcd657e8f5de69ef82781165.pngDalvik 虚拟机的原理就是每次运行 app 的时候都需要通过 JIT 编译器将 dex 编译为 odex 文件,这样就会导致每次运行应用都很慢, 所以在 4.4 之后 Android 引入了 ART 虚拟机,它是在安装应用的时候或者重启手机的时候通过 dex2oat 把应用的 dex 先编译成 elf 文件(oat),等执行的时候就直接运行了,优化了 app 的运行速度。不过这样就会导致 app 安装或重启的时候很慢,所以 Android 7.0 之后安装或重启手机的时候又恢复了原先的 JIT 运行机制,不过这次会比 4.4 版本之前的多一个 profile 文件,用于记录 JIT 编译后的缓存信息,把 dex2oat 的时机放在了第一次运行 app 的时候或者手机空闲的时候读取安装应用进行 dex2oat 编译。

下面来个例子

Hello.java

public class Hello{  public void main(String[] args){    add();  }  public int add(){    return 1 + 2;  }}

通过 javac Hello.java 之后转换为 Hello.class ,接下来通过 dx 工具转换为 dex 文件

dx --dex --output Hello.dex Hello.class

其中 Hello.dex 可以随便命名。最终可以得到 Hello.dex 文件,接着生成 odex 文件,odex 文件需要手机上面生成,所以需要先电脑连接手机

adb push Hello.dex /data/local/tmp
dex2oat --dex-file=/data/local/tmp/Hello.dex --oat-file=/data/local/tmp/Hello.odex  --instruction-set=arm64 --app-image-file=/system/framework/oat/arm64/Hello.art  --runtime-arg -Xms64m --runtime-arg -Xmx128m
  • --dex-file 指定要编译的dex文件

  • --oat-file 指定要输出的odex文件

  • --instruction-set 指定cpu架构

  • --app-image-file 指定odex相对应的art文件

  • --runtime-arg 指定dex2oat运行时的参数,如果编译时发生内存不足,可以把Xms和Xmx调大

生成 oat 文件命令

dex2oat --dex-file=/data/local/tmp/Hello.dex --runtime-arg -Xms64m --runtime-arg -Xmx64m --oat-file=/data/local/tmp/Hello.oat

oat 和 odex 都是给虚拟机执行的文件,而我们看的一般都是 dex 文件。通过 010 Editor 可以查看到 Hello.dex 文件结构为

406467477b3dbcae360297dc8f5ee170.png

如果要查看 class 字节码,则是通过 javap 命令

javap -v Hello.class
Classfile /D:/Hello.class  Last modified 2020-11-4; size 326 bytes  MD5 checksum fcfe915ce13b7c5e91106e2d57bf8629  Compiled from "Hello.java"public class Hello  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Methodref          #4.#15         // java/lang/Object."":()V   #2 = Methodref          #3.#16         // Hello.add:()I   #3 = Class              #17            // Hello   #4 = Class              #18            // java/lang/Object   #5 = Utf8                  #6 = Utf8               ()V   #7 = Utf8               Code   #8 = Utf8               LineNumberTable   #9 = Utf8               main  #10 = Utf8               ([Ljava/lang/String;)V  #11 = Utf8               add  #12 = Utf8               ()I  #13 = Utf8               SourceFile  #14 = Utf8               Hello.java  #15 = NameAndType        #5:#6          // "":()V  #16 = NameAndType        #11:#12        // add:()I  #17 = Utf8               Hello  #18 = Utf8               java/lang/Object{  public Hello();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."":()V         4: return      LineNumberTable:        line 1: 0  public void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC    Code:      stack=1, locals=2, args_size=2         0: aload_0         1: invokevirtual #2                  // Method add:()I         4: pop         5: return      LineNumberTable:        line 3: 0        line 4: 5  public int add();    descriptor: ()I    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: iconst_3         1: ireturn      LineNumberTable:        line 6: 0}SourceFile: "Hello.java"

字节码学习

  • Constant pool 常量池

它包括字面量(字符串,final 常量)和符号引用(类和接口的名称,字段的名称和描述符号,方法的名称和描述符)

#1 = Methodref          #4.#15         // java/lang/Object."":()V

Methodref 表示方法的申明,#4,#15 指的是常量的位置,结果就是

// java/lang/Object."":()V

对应的是 Object 的 init 方法,最后 V 的意思是方法的返回值类型为 Void。剩下的其他常量也类似的申明。

  • stack

最大操作数栈,JVM运行时会根据这个值来分配栈帧(Frame)中的操作栈深度,此处为1

  • locals

局部变量所需的存储空间,单位为Slot, Slot是虚拟机为局部变量分配内存时所使用的最小单位,为4个字节大小。

  • args_size

方法参数的个数,这里是1 指的是实例方法的隐藏参数 this

  • LineNumberTable 

该属性的作用是描述源码行号与字节码行号(字节码偏移量)之间的对应关系。

  • 方法调用命令

  1. invokevirtual   调用实例方法,指的是虚方法,如上面的 add 方法

  2. invokestatic 调用类方法,指的是 static 方法

  3. invokeinterface 调用接口方法,运行时搜索合适方法调用

  4. invokespecial 调用调用特殊实例方法,包括实例初始化方法、父类方法

  5. invokedynamic 由用户引导方法决定,运行时动态解析出调用点限定符所引用方法

了解完 class 的相关知识,接下来应该就是了解 AspectJ 和 ASM 框架是如何修改 class 字节码的,具体如何操作,将在下一篇学习?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值