JVM Hotspot 虚拟机与 Dalvik&ART 虚拟机堆栈的区别

4 篇文章 0 订阅

该篇内容会基于 JVM 运行时数据区(栈和堆)JVM GC 两篇文章的理论知识上讲解,所以建议看完理解后再学习该篇文章会对知识的学习比较有帮助。

Hotspot 虚拟机与 Dalvik 虚拟机的区别

JVM 运行时数据区(栈和堆) 有简单提到,Hotspot 虚拟机是基于 JVM 标准开发的虚拟机,而 Android 并不是使用的 Hotspot 虚拟机,而是使用了 Dalvik 虚拟机,在 Android 5.0 后被替换为 ART 虚拟机。Dalvik 是一款不是 JVM 的 JVM 虚拟机,本质上它没有遵循 JVM 规范

Android 为什么不使用 Hotspot 虚拟机,而是自己定制了 Dalvik 虚拟机呢?这就涉及到 Java 体系和 Android 体系的对比区别。

在 Java 体系使用 Java 语言,是为了能更好的推广 Java 语言,能够兼顾到更广的应用范围,注重的是能够跨平台,能够在各种各样的设备上移植,不需要硬件设备对 Java 语言的支撑。

在 Android 体系因为 设备目标明确 就是使用在 Android 手机上,也不需要依赖于硬件,因为 硬件体系规范明确

Dalvik 虚拟机与 Hotspot 虚拟机的区别:

  • 不直接运行 class 字节码文件,执行的是编译后的 dex 字节码文件

  • 它的结构基于寄存器指令集结构,而不是 JVM 的栈指令集结构

Android 内存管理模型

JVM 运行时数据区:

在这里插入图片描述

Android 内存模型:

在这里插入图片描述

上图是 Android 的内存模型,相比 JVM,Android 将内存分为用户空间和内存空间,且用户空间是进程隔离的。

JVM 堆内存初始大小是 运行内存大小 / 64,最大内存大小是 运行内存 / 4,大小可能上好几个 GB,Android 每一个应用分配的内存并不大,只有 128-256 MB,当然这个数值不同的厂商定制化设定的数值也不同。

接下来同样的也会对比 JVM 栈和堆讲解 Android 的堆栈。

JVM 与 Dalvik 虚拟机在栈的区别

在这里插入图片描述

每个线程都会开辟一个虚拟机栈,每个方法对应一个栈帧,无论是 JVM 还是 Dalvik&ART 虚拟机在栈结构上都是这样的。栈区就是管理方法的运行。

那怎么理解 JVM 使用的栈指令集结构,Dalvik&ART 使用的寄存器指令集结构?

栈指令集结构在 JVM 主要代指的是栈帧中的局部变量表和操作数栈:

在这里插入图片描述

可以看到上图中,一个栈帧中无论是局部变量表还是操作数栈,都是基于栈结构。指令执行的最小单位是一个字节 8 位,这样能兼容到所有设备;方法运行时局部变量表和操作数栈会不断的入栈出栈。这种设计能够体现跨平台的优势。

在 Android 为了能够一次处理更多的指令有更快的运行速度,Dalvik&ART 虚拟机参考 CPU 结构,在栈帧内部将局部变量表和操作数栈的概念去除了,换为寄存器结构(也可以理解这里的寄存器就是局部变量表+操作数栈),寄存器能够一次操作 2/4/6 个字节单位的指令,用空间换时间。

在 CPU 中寄存器是其中一个组成部分,它能够有限的暂存指令、数据和位址:

在这里插入图片描述

相比 JVM 栈指令集结构计算需要多次入栈出栈,用寄存器结构计算时,就是将两个内存地址的数据通过 ALU 算数逻辑单元计算后,将结果存到另一块内存空间。与 JVM 相比这样的设计在指令数和数据移动次数上明显减少了

需要注意的是,无论是基于寄存器指令集结构还是基于栈指令集结构,它们都是有使用栈结构,一个线程还是一个虚拟机栈,还是用的栈帧,区别是栈帧内部结构的不同,JVM 栈帧内部是局部变量表和操作数栈,而 Dalvik&ART 虚拟机是寄存器

AOT(Ahead Of Time)预先编译机制

在讲解 Dalvik&ART 虚拟机堆区前,因为会涉及到 AOT 预先编译机制的内容,所以先提前了解下什么是 AOT。

在这里插入图片描述

Dalvik 虚拟机执行的是 dex 字节码文件,dex 字节码文件的生成经过 将 Java 文件编译为 class 字节码文件 -> class 字节码文件通过 dx 工具编译为 dex 字节码文件 -> 应用安装时 dex 字节码文件通过 dexopt 工具优化为 odex 字节码文件

从 Android 2.2 开始支持 JIT(Just In Time)即时编译,即在程序运行过程中选择经常执行的代码进行编译或优化。

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

那 ART 执行的本地机器码是哪里来的?这就涉及到 dex2oat。

在这里插入图片描述

每一个 apk 应用都是一个 Dalvik 虚拟机,在应用安装时会进行一次优化,将 dex 字节码优化生成 odex 文件。ART 虚拟机将应用的 dex 字节码翻译成本地机器码的最恰当 AOT 预先编译时机也是发生在应用安装的时候。在安装时,ART 使用设备自带的 dex2oat 工具编译应用,dex 中的字节码将被编译成本地机器码。

虽然 AOT 能在应用运行时更快的运行,但这带来的问题就是应用安装会比较慢。所以在 Android 更高系统版本也提供了优化方案:

在这里插入图片描述

在 Android. 7.0 及更高版本混合使用了 AOT 预先编译、解释和 JIT。

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

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

JVM 与 Dalvik 虚拟机在堆的区别

在这里插入图片描述

Android 因为数据种类比较多,有 Java、C/C++、Bitmap、预编译好的代码等,所以相比 JVM 只有一块堆内存,Android 根据存储的数据类型不同,它将堆拆分成了四个部分:Image Space、Zygote Space、Allocation Space 和 LargeObject Space。

Image Space

Image Space 是用于存放系统预加载类,这些系统预加载类是通过 AOT 预先编译好并存放在 oat 文件中,每次开机启动时会把这些类映射到 Image Space。类比 JVM 堆区可以理解为是方法区。这块内存不进行垃圾回收

Zygote Space

Zygote Space 是存放管理 Zygote 进程在启动过程中预加载和创建的各种对象和资源,会进行垃圾回收。

Allocation Space

Allocation Space 是我们主要关注的一块内存空间,它是 Java 或 C/C++ 代码的对象内存分配。类比 JVM 堆区就是年青代和老年代两个内存区域:

在这里插入图片描述

Allocation Space 大小是有限制的,一般是 12MB/24MB/36MB/48MB,不同的厂商定制化这块内存的空间各有不同,大多是 12MB/24MB。这个内存大小并不大,也因为有这个大小限制,如果对象过多就会频繁 GC。

LargeObject Space

LargeObject Space 是用于分配大对象的内存。类比 JVM 堆区就是老年代。

当满足以下一个条件时对象内存会在 LargeObject Space 分配,否则在 Allocation Space 上分配:

  • 对象需要分配的内存大小 >= 12KB(内存一页 page 是 4 KB,12 KB 也就是三页 page)

  • 对象是基本数据类型数组,即 byte[]、int[]、boolean[] 等;图片也是 byte[] 所以也是会在这里分配

JVM 与 Dalvik 虚拟机在 GC 策略的区别

JVM 会有 Minor GC、Major GC 和 Full GC 三种策略:

  • Eden 区满了,触发 Minor GC

  • old 区空间不足了,先触发 Minor GC,如果内存还不够则触发 Major GC

  • System.gc()、方法区空间不足、old 区空间不足、从年青代推到老年代的对象 > old 区可用内存,触发 Full GC

Dalvik 同样有三种 GC 策略:

  • Sticky GC:只回收上一次 GC 到本地 GC 之间申请的内存。其实就是 CMS 回收浮游垃圾

  • Partial GC:局部垃圾回收,会回收 Allocation Space 和 LargeObject Space 的内存垃圾

  • Full GC:全局垃圾回收,回收除了 Image Space 之外的内存垃圾

性能指标GC 策略对比
GC 暂停时间Sticky GC < Partial GC < Full GC
回收垃圾的效率Sticky GC > Partial GC > Full GC

GC 时间越长对应用影响越大,因为 GC 伴随着 STW(Stop The World),所以也就会出现卡顿现象。

Java 四种引用类型

GC 回收也涉及到对应的引用类型,Java 有四种引用类型:强引用、软引用、弱引用、虚引用。

  • 强引用:普通 new 对象就是使用强引用,强引用必须是对象不可达情况下才会回收

  • 软引用:当内存不足时,软引用会被回收;系统内存不足时,就算可达也会被回收

  • 弱引用:只要遇到垃圾回收,就会被回收掉

  • 虚引用:如果一个对象仅持有虚引用,那么它就和没有引用一样,在任何时候都可能被 GC 回收

JVM 与 Dalvik 虚拟机对象分配流程的区别

在这里插入图片描述

JVM 对象分配流程如下:

  • new 出来的对象进 Eden(大对象直接进老年代)

  • Eden 区放满了,再放就会开启一个 GC 线程(Minor GC)来回收垃圾

  • 把 Eden 区中非垃圾对象复制到 Survivor 的 From 区,剩下的直接全部清除

  • 继续 new 对象时,如果 Eden 区又满了,GC 来了就把 Eden 和 From 区存活的对象复制到 To 区,对象的分代年龄 +1

  • 每次 Eden 满的时候,就在 Eden+From 和 Eden+To 中来回复制

  • 对象年龄到老年代阈值,就进入老年代

  • 老年代放满了,就会发生 Full GC,对堆进行全面 GC

在这里插入图片描述

Dalvik 对象分配流程如下:

  • 首先会进行一次 Sticky GC,GC 完成后尝试分配;如果分配失败,则选择 Partial GC,GC 完成后再尝试分配;还是分配失败,则选择 Full GC,GC 完成后再尝试分配;如果还是不能分配,进入下一阶段

  • 允许堆进行增长的情况下进行对象分配(可能一开始是 128 MB,最大是 256 MB,堆内存增长后再尝试分配)

  • 经过两个阶段还不能分配,进行一次允许回收软引用的 GC 进行对象分配;如果还不能分配,OOM

将上面的三个阶段可以简单理解为:

  • 内存是否足够,不够,GC 一次回收浮游垃圾

  • 还不够,GC 一次局部回收(Allocation Space 和 LargeObject Space 的内存回收)

  • 还不够,Full GC 一次(除 Image Space 之外的内存回收)

  • 还不够,堆扩容

  • 还不够,软引用回收

  • 还不够,OOM

可以发现 Android 相比 JVM,在对象分配流程上会最大限度的保证不出现 OOM 的情况下,充分利用每一份空间,但是 当堆内存只要达到 85% 左右就会出现频繁 GC

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值