高亮的杂货铺

JVM从0到1-垃圾回收概念

2020-03-15 · 5 min read
JVM Java

分代收集理论

分代收集理论是建立在经验法则上的,基于两个假说

  1. 若分代假说:绝大多数对象都是照生夕灭的
  2. 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
    设计者一般把Java堆划分为新生代和老年代两个区域,因而才有了“Minor GC”,"Major GC", "Full GC"等分类类型的划分。

假如现在要进行一次只局限于新生代区域的GC,但新生代内存中的对象是完全有可能被老年代所持有的,为了找到该区域存活的对象,不得不在固定的GC Roots之外,再额外遍历一遍整个老年代来确定正确性,反之也要一样,但是这无疑导致了很大的性能负担。 因此,为了解决这个问题,就需要对分代收集理论添加第三条经验法则:
3. 跨代引用假说:跨代引用相对于同代引用来说,仅占极少数

依据这个假说,我们就不应该再为了少量的跨代引用区扫描整个老年代了,也不必浪费空间专门记录每个对象是否存在以及存在哪些跨代引用,只需要再新生代上建立一个全局的数据结构(记忆集,Remebered Set),这个结构把老年代划分为螺杆个小块,标识出老年代中哪一块内存会存在跨代引用,此后发生Minor GC时间,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots中进行扫描。虽然这种方法需要在对象改变引用关系时,维护记录数据的正确性,而导致一部分运行时开销,但是相较于扫描整个老年代老说还是更划算的。

定义:

  • 新生代收集(Minor GC/Young GC): 指的目标是只是新生代的收集
  • 老年代收集(Major GC/Old GC): 只目标只是老年代的收集,目前只有CMS收集器会有单独收集老年代的行为。
  • 混合收集(Mixed GC): 指的是收集整个新生代以及部分老年代的垃圾收集,目前只有G1收集器会有这种行为
  • 整堆收集(Full GC): 收集整个Java堆和方法区的垃圾收集

标记-清除算法

最基础的收集算法, 首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。

回导致内存不连续。

标记复制算法

将内存空间分为两块,标记之后直接将存货的对象复制到新的空间,再把已使用过的一半空间直接清空。
由于新生代中的对象大部分都熬不过第一轮收集, 因此并不需要按照1:1的比例来划分新生代的内存空间,以减少浪费。

因此,提出了一种新的半区复制分代策略,现在称之为“Appel式”回收 HotSpot虚拟机的Serial、ParNew等新生代收集器均采用这种策略。

具体做法是,将新生代划分为Eden空间和两块较小的Survivor空间,每次复制只使用Eden空间和其中一块Survivor。 发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次复制到另一块Survivor空间上,然后直接清理掉Eden和已经使用过的那一块Survivor空间。 HotSpot虚拟机默认的Eden和Survivor的大小比例是8:1,即每次新生代中可以用内存空间为整个新生代空间的90%,只有一个Survivor空间,即10%的新生代是被浪费的。但是这是根据经验法则来设定的,存在一种情况的安全设计,例如当Survivor空间不足以容纳一次Minor GC后存活的对象时,就需要以来其他内存区域(实际上大多是老年代)来进行分配担保。

标记整理算法

在标记之后将存活的对象重新排列。 但是这种操作是一种很重的操作,需要暂停全部用户应用程序才能进行。 “Stop The World”。这样提高了回收时的复杂度,但是降低了对象内存分配时的复杂度。 对象分配的频率是一般是高于垃圾收集频率的。 HotSpot里,Parallel Scavenge收集器是基于标记-整理算法的,关注延迟的CMS是基于标记清楚算法的。