JVM vs. DVM 的核心区别
- 基于的架构不同
- JVM基于栈,需要去栈中读写数据,所需的指令更多,导致速度变慢,对于性能有限的移动设备不是很适合
- DVM 基于寄存器, 指令紧凑,更简洁,可以减少指令分派、取指和解释执行的次数,降低CUP开销,因此更适合移动设备有限的CPU和内存环境
解释器执行每条字节码时,都需要经过取指、解码、分派和执行。取指是读取下一条字节码,分派是根据操作码跳转到对应的处理逻辑,解释执行则是由解释器完成该指令表示的操作。寄存器型字节码通常用更少的指令完成相同任务,因此可以减少解释器循环和指令分派次数,这在以解释执行为主的早期移动设备上有一定性能优势。
- 执行的字节码不同
- JVM 会从
.class文件 或 打包的.jar文件 中获取相应的字节码。
每个.class文件里面包含了该类的常量池、类信息、属性等。当JVM加载.jar文件的时候,会加载里面的所有的.class文件,JVM 的这种方式很慢,对内存有限的移动设备不适合。 - DVM 会从
.dex文件 读取指令和数据。
在.apk文件中只包含了一个.dex文件,这个.dex文件会将所有的.class里面包含的信息全部整合在一起,并且 dex 工具会去除很多.class文件的冗余信息,将 .class 文件整合到 .dex 文件中, 减少了 I/O 操作,加快了类的查找速度。
- DVM 早期没有使用 JIT 编译器
JVM 使用了 JIT 编译器(Just In Time Compiler,即时编译器),而 DVM 早期没有使用 JIT 编译器。
早期的 DVM 每次执行代码,都需要通过解释器将 dex 代码编译成机器码,然后交给系统处理,效率不是很高。
为了解决该问题,后期,DVM 使用了 JIT 编译器,它会对多次运行的代码(热点代码)进行编译,生成相当精简的本地机器码(Native Code),这样在下次执行到相同逻辑的时候,直接使用编译之后的本地机器码,而不是每次都需要编译。
Dalvik 与 ART 的核心区别
-
编译方式不同(核心)
- Dalvik :采用 JIT(即时编译),每次运行应用时,字节码实时编译为机器码,程序运行效率低。(本质上还是解释执行,边解释边执行)
- ART :默认采用 AOT(预编译),安装时一次性将字节码编译为本地机器码,运行流畅、省电,但安装耗时、占用空间大。
- Android 7.0+ 混合模式:ART 加入 JIT 作为补充,安装时不将字节码全编译为机器码,运行时识别编译热点代码为机器码,平衡安装速度、空间和运行性能。
-
CPU 架构支持
- Dalvik 仅支持 32 位 CPU;
- ART 支持 64 位且兼容 32 位,是 Dalvik 被淘汰的重要原因。
-
垃圾回收(GC)优化
- ART 对 GC 进行了改进,堆内存划分更精细,回收效率更高,减少了应用卡顿。
JIT 是什么?
JIT 出现之前或尚未触发时,JVM 通过解释器逐条读取和分派字节码,执行解释器内部已有的本地处理逻辑;当方法或循环成为热点后,JIT 会把整段字节码编译成本地机器码,后续直接执行机器码,不再逐条解释。
每条字节码都包含一个 opcode ,有些指令后面还带操作数。根据 操作码 选择对应处理逻辑的过程,就是 指令分派(dispatch)。
text
没有 JIT:
字节码
→ 解释器逐条分派
→ 本地 handler
→ CPU
有 JIT:
前期:
字节码
→ 解释器
热点后:
字节码
→ JIT 编译
→ 本地机器码
→ CPU 直接执行
"解释执行 + JIT 编译"的混合模式
- Java 源代码先通过
javac编译成.class字节码。 - 程序刚启动时,解释器逐条解释字节码并执行,因此启动较快。
- JVM 会统计代码的执行频率。
- 当某个方法或循环成为热点代码后,JIT 编译器会把它编译成本地机器码。
- 后续再次执行热点代码时,直接运行机器码,从而提高性能。