JVM 知识梳理 (二) GC算法
一、前言
垃圾收集(Garbage Collection ),简称GC。是Java 的一种垃圾自动回收机制,但程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言规范并不保证 GC 一定会执行。
判定哪些对象需要被GC,主要有两种方法,引用计数法和可达性算法。
GC算法包括引用计数法、标记—清除、标记—压缩、复制算法。
二、判定哪些对象需要被GC
引用计数法
引用计数法,也叫直接垃圾回收法,是老牌垃圾回收算法,当GC触发时,通过对象的引用来计算要不要回收。
给每个对象分配一个引用计数器,只要该对象被应用,计数器+1.取消引用就-1。
当GC触发时,如果该对象的引用计数器为零,那这个对象就是垃圾。
优点
即时回收,因为对象知道自己什么时候没用
缺点
引用和去除引用伴随着加法减法,影响性能。还有就是很难处理循环引用。
需要注意的是,在主流虚拟机中,并未涉及引用计数法。
可达性算法(引用链法)
可达性算法,也叫间接垃圾回收算法,该算法的思想是:从一个被称为 GC Roots 的对象开始向下搜索,如果一个对象到 GC Roots 没有任何引用链相连时,则说明此对象不可用。
三、GC算法
标记—清除
标记—清除算法是现代垃圾回收算法的思想基础。
这个算法将垃圾回收分为了两个阶段,标记阶段和清除阶段。
1.在标记阶段,首先通过根节点,标记所有从根节点开始可达对象,这时的可达对象代表还“有用”,而未标记的对象就是未被引用的垃圾对象。
2.在清除阶段,清除未被标记的垃圾对象。
优点
实现起来简单,不需要额外的空间
缺点
逐渐产生被细化的分块,不久后就会导致无数的 小分块散布在堆的各处,并且在分配对象的时候还得先遍历那些内存块可以用。
标记—压缩
标记-压缩算法适合用于存活对象较多的场合,如老年代。
它在标记-清除算法的基础上做了一些优化,即在标记—清除算法后,将所有存活的对象压缩到内存的一端,然后清理这个边界外的所有空间
缺点:清除算法中,清除阶段也要搜索整个堆,不过搜索 1 次就够了。但 GC 标记 - 压缩算法要搜索 3 次,这样就要花费约 3 倍的时间,这是一个相当巨大的缺陷,特别是堆越大,所消耗的成本也就越大(看图)
优点:堆利用效率高。
而且 GC 标记 - 压缩算法不会出现 GC 复制算法那样只能利用半个堆的情况
复制算法
复制算法,在标记清除的的基础上改进而来的,适用于对象存活度低的场景,如年轻代。
思想是将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
需要注意的是,在复制算法下,两个survivor区(From和To区)同⼀时间会有⼀个满⼀个空,是交替的,并且空的总是To区。
优点:
1、优秀的吞吐量: GC 标记 - 清除算法消耗的吞吐量是搜索活动对象(标记阶段)所花费的时间和搜索整体 堆(清除阶段)所花费的时间之和。
另一方面,因为 GC 复制算法只搜索并复制活动对象,所以跟一般的 GC 标记 - 清除算 法相比,它能在较短时间内完成 GC。也就是说,其吞吐量优秀。
2、高速分配:GC 复制算法不使用空闲链表。这是因为分块是一个连续的内存空间。比起 GC 标记 - 清除算法和引用计数法等使用空闲链表的分配,GC 复制算法明显快得多。
3、不会发生碎片化
缺点:
浪费内存空间,因为用这种算法,必须保证To区是空的。假如极端情况下对象100%存在,内存空间就会不够。所以在对象存存活度低时适合使用,我们知道年轻代是存活度低,所以事实上就是使用的复制算法。
总结
没有最好的算法,但有最合适的算法。
分代收集
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保。
参考
B栈狂神说JVM入门
JVM GC算法
还没有评论,来说两句吧...