浅析JVM(三)|浅析JVM(三) GC

浅析JVM(三)|浅析JVM(三) GC
文章图片
前言
JVM 是 Java Virtual Machine(Java虚拟机)的缩写,它是一种规范,HotSpot VM是其最主流的实现(其他实现),通常我们讨论JVM如果没有特意说明是何种实现,便指的是HotSpot VM。JVM也并非仅支持Java语言,任何可编译为字节码的编程语言都可以运行在JVM上,例如前不久谷歌在 I/O 2017宣布将作为 Android 开发 First-Class 语言的 Kotlin。GC 是 JVM 引以为傲的重要特性之一,本文将结合作者自己的理解对GC 与 GC 算法做一粗浅的解析,不对之处,望指出,共勉。
关于GC
GC,即垃圾回收(英语:Garbage Collection,缩写为GC)是一种自动的内存管理机制,Java编程语言之所以能屹立二十余载仍广受欢迎和JVM提供的GC机制密不可分(Java并非是第一门使用GC的编程语言,1960年诞生于MIT的Lisp是第一门真正使用GC的语言)。在C、C++中开发者通常需要自行分配内存、释放内存,稍有不慎便会带来麻烦,有了GC之后Java开发者则无需去担心这些事情,可以尽情的“new对象"申请内存,而GC会自动为开发者做“善后工作”清理对象并释放内存。不过万事有利必有弊,在享受GC带来的便利性的同时也一定程度上牺牲了编程的灵活性。
GC是如何工作的
不同GC的实现细节各有不同,但总的来说基本所有的GC无外乎在做下面两件事:

  1. 标记存活对象
  2. 回收垃圾对象
标记存活对象 所有现代的GC都通过可达性分析算法来标记存活对象,这个算法的基本思路是通过一些被称为 “GC Roots” 的对象作为起点,从这些起点进行遍历搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是无用的垃圾对象,也就是待回收的对象,否则为存活对象。
GC 通常将下列对象将被作为GC Roots:
  • 虚拟机栈(栈帧中的本地变量表)引用的对象
  • 方法区中类静态变量引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈 JNI 引用的对象
浅析JVM(三)|浅析JVM(三) GC
文章图片
可达性分析算法
存活对象在上图中被标记为蓝色。当标记阶段完成了之后,所有的存活对象都已经被标记完了。其它的那些(上图中灰色的那些)也就是GC根对象不可达的对象,也就是说你的应用不会再用到它们了。这些就是垃圾对象,回收器将会在接下来的阶段中清除它们。
【浅析JVM(三)|浅析JVM(三) GC】关于标记阶段有几个关键点是值得注意的:
开始进行标记前,需要先暂停应用线程,否则如果对象图一直在变化的话是无法真正去遍历它的。暂停应用线程以便JVM可以尽情地收拾家务的这种情况又被称之为安全点(Safe Point),这会触发一次Stop The World(STW)暂停。触发安全点的原因有许多,但最常见的应该就是垃圾回收了。暂停时间的长短并不取决于堆内对象的多少也不是堆的大小,而是存活对象的多少。因此,调高堆的大小并不会影 响到标记阶段的时间长短。
回收垃圾对象
  • 标记-清除法
浅析JVM(三)|浅析JVM(三) GC
文章图片
标记-清除法
“标记-清除”法是最简单的的垃圾回收算法。在标记出所有的存活对象后,将其他对象进行回收即可,如上图所示。不过标记-清除法有很大的不足,它会导致大量零碎的内存空间碎片,导致大容量对象不容易获得连续的内存空间,而造成空间浪费,后面两种算法对此进行了改进。
  • 标记-清除-整理法
浅析JVM(三)|浅析JVM(三) GC
文章图片
标记-清除-整理法
“标记-清除-整理”法对“标记-清除”法进行了改进,它在清理完成后将所有标记的也就是存活的对象依次移动到内存区域的开始位置进行整理以便消除内存空间碎片,如上图所示。但这种方法的缺点就是GC暂停的时间会增长,因为GC需要更新所有存活对象的引用地址。
  • 标记-复制法
浅析JVM(三)|浅析JVM(三) GC
文章图片
标记-复制法
“标记-复制”法与“标记-清除-整理”法非常类似,它们都会将所有存活对象的内存空间重新进行分配。区别在于标记-复制法在标记出所有的存活对象后把它们复制到另一块内存空间并将原内存空间一次性清除(复制与清除可同时进行),如上图所示,这样便可改善“标记-清除-整理”法GC暂停的时间过长的缺点。不过它也有缺点,就是需要一块能容纳下所有存活对象的额外的内存空间,这是典型的拿空间换时间,在计算机世界中空间与时间总是一对矛盾体。
  • 分代收集法
浅析JVM(三)|浅析JVM(三) GC
文章图片
新生代&老年代 由于前面的三种GC算法都各有优劣,并没有一种完美的算法。所以,在JVM中采用了分代收集法,根据对象的存活周期的不同将内存划分为几部分。通常把堆分成新生代和老年代,这样可以根据各代的特点采用最适合的收集算法。
在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,使用“标记-复制”法,只需要复制少量的对象就可以完成收集,成本小,速度快。
在老年代中,对象存活率比较高,使用“标记-复制”法的话占用的内存成本过大,使用“标记-清除”、和“标记-清除-整理”法来进行回收更为合适。
注:上面都是一些基础的常见GC算法,其他高级算法的由于作者目前为止还没有接触到,只能由你们自己去探索了。
参考
  • Java Garbage Collection handbook
查看《浅析JVM》系列文章目录

    推荐阅读