JVM 垃圾收集器

时间:2023-01-02 17:56:20

GC是jvm自动内存管理机制的具体实现。在HotSpot中,GC的工作主要划分为两大块,分别是内存的动态分配和垃圾回收。jvm中存活对象的生命周期具有两极化,因此该采取不同的垃圾收集策略,分代收集由此诞生。java堆内存划分为新生代(YoungGen)和老年代(OldGen),其中新生代又划分为Eden区、From Survivor区和To Survivor区。

1、标记算法

垃圾标记算法,是用来区分哪些是已经死亡的对象,标记完后,GC执行垃圾回收,释放掉占用的内存。目前常用的垃圾标记算法有两种,分别是引用计数算法和根搜索算法。

1.1引用计数算法

引用计数算法会为程序中每个对象创建一个私有的引用计数器,当对象被其他存活对象引用时,计数器值则加1,不再被引用便减1,当计数器的值为0时,表示对象不再被任何存活对象引用,可以被标记为垃圾对象。但是如果死亡的对象之间存在相互引用时,引用计数器的值永远不为0,会导致GC在执行内存回收时无法释放掉无用对象占用的内存,会引发内存泄漏。

1.2根搜索算法

根搜索算法是以根对象集合作为起始点,按照从上至下的方式搜索被根对象集合连接的目标对象是否可达,如果目标对象不可达,就表示该对象已经死亡,便可以在instanceOopDesc的Mark Word中标记为垃圾对象。
这也是jvm使用的标记算法。

2、分代收集算法

jvm中常用的三种垃圾收集算法有标记—清除算法、复制算法、标记—压缩算法

2.1 标记-清除算法

标记—清除算法是一种基础、常见的垃圾收集算法,它将垃圾回收划分为垃圾标记和内存释放阶段,执行效率低下,而且由于回收的对象占用的内存可能是不连续的内存块,会产生内存碎片,导致后续没有足够可用内存分配给较大的对象。

2.2 复制算法

复制算法被广泛用于新生代。基于内存分代,新生代被划分为Eden区、From Survivor区和To Survivor区。当Minor GC(新生代垃圾回收)时,Eden区中存活对象会被复制到To Survivor区,而且已经经历过一次Minor GC并在From Survivor区存活下来的对象如果还年轻也会被复制到To Survivor区。在执行完Minor GC后,Eden区和From Survivor区会被清空,存活下来的对象则会复制到To Survivor区,然后From Survivor区和To Survivor区互换。如果From Survivor区对象的分代年龄超过”-XX:MaxTenuringThreashold“指定的阈值,将直接晋升到老年代;其次当To Survivor区内存使用达到阈值时,存活对象也直接晋升到老年代。

2.3标记-压缩(整理)算法

标记—压缩(整理)算法,用于老年代内存的GC,当标记处内存中的垃圾对象后,该算法会将所有的存活对象都移动到一个规整且连续的内存空间,然后执行Full GC(老年代垃圾回收,也称为Major GC)回收无用对象占用的内存。当成功执行压缩后,已用和未用内存都各自一边,彼此维系着一个记录下一次分配起始点的标记指针,当为新对象分配内存时,可以使用指针碰撞修改指针偏移量将新对象分配在第一个内存位置。

3 垃圾收集器

jvm实现了多种垃圾收集器,包括Serial/Serial Old收集器、Parallel/Parallel Old收集器、CMS(Concurrent-Mark-Sweep)收集器,以及jdk7版本提供的G1(Garbage-First)收集器。在jvm运行过程中,新生代和老年代各自的GC需要组合在一起执行垃圾回收。
jvm中实现的垃圾收集器分为串行和并行回收。串行回收是指当多个cpu可用是,也只有一个cpu用于执行垃圾回收操作,且执行垃圾回收时,程序中的工作现场会被暂停,当垃圾收集完成后才会恢复之前被暂停的工作线程。

3.1 串行回收:Serial收集器

Serial收集器用于新生代垃圾收集,采用复制算法、串行回收和”Stop-the-World“机制执行内存回收。Serial收集器默认作为HotSpot中Client模式下的新生代垃圾收集器。
除新生代外,Serial收集器还提供用于老年代垃圾收集的Serial Old收集器。Serial Old收集器也采用串行回收和”Stop-the-World“机制,内存回收算法使用的是标记—压缩算法。

3.2 并行回收:ParNew收集器

ParNew收集器是Serial收集器的多线程版本。ParNew收集器在新生代也是采用复制算法和”Stop-the-World“机制。ParNew收集器+CMS收集器组合是Server模式下内存回收的最佳选择

3.3 程序吞吐量优先:Parallel收集器

Parallel收集器采用复制算法、并行回收和”Stop-the-World“机制,和ParNew收集器不同,Parallel收集器可以控制程序吞吐量大小,因此称为吞吐量优先的垃圾收集器。
Parallel收集器也提供了用于老年代垃圾收集的Parallel Old收集器,Parallel Old收集器采用标记—压缩算法、并行回收和”Stop-the-World“机制

3.4 低延迟:CMS(Concurrent-Mark-Sweep)收集器

CMS天生为并发而生,低延迟是它的优势,不过垃圾收集算法并没有采用标记—压缩算法,而是采用标记—清除算法,也因为”Stop-the-World“机制出现短暂暂停。
CMS收集器的回收周期称为初始标记的阶段开始,初始标记阶段,程序中所有的工作进程会因”Stop-the-World“机制出现短暂的暂停,这个阶段的主要任务是标记出内存中那些和根对象连接的目标对象是否可达,一旦标记完成就会恢复之前被暂停的所有工作线程。接下来将会进入并发标记阶段,此阶段会将之前不可达对象标记为垃圾对象。在CMS执行内存回收前,由于并发标记阶段,程序的工作线程和垃圾收集线程同时运行或交叉运行,因此CMS会进行再次标记阶段,程序会因”Stop-the-World“机制再次短暂暂停,以确保新产生的垃圾对象能够被标记。当经历过初始化标记、并发标记和再次标记三个阶段后,CMS最终会进入到并发清除阶段执行内存回收。
尽管CMS收集器采用并行回收,但是初始标记和再次标记两个阶段需要”Stop-the-World“机制暂停程序中的工作进程,不过暂停时间不会太长。

3.5 区域化分代式:G1(Garbage-First)收集器

G1是为了替代CMS收集器的,是一款基于并行和并发、低延迟以及暂停时间更加可控的。G1没有使用传统物理隔离的新生代和老年代布局方式,而是选择jvm堆内存划分为2048个大小相同的对立Region块,每个Region块之间可能是不连续的,大小控制在1MB到32MB之间,这些Region会被申请作为伊甸区、Survivor区、年老区的逻辑表示,存活的对象会被从一个Region copy到其他分区,Region的内存可以并行收集而不需要停止应用线程。其他没有被申请的Region被称为 Humongous regions,该Region用来持有那些大于一个普通region一半以上的大对象,但是对于大对象的收集不会被优化。
JVM 垃圾收集器

G1收集器执行过程:
- 初始标记阶段(Initial-Mark),程序中所有工作现场都因”Stop-the-World“机制暂停,该阶段主要标记Root-Region。
- 根区域扫描阶段(Root-Region-Scanning),主要任务是扫描Root-Region中一个引用老年代的一些Region块。
- 并发标记阶段(Concurrent-Marking),主要任务是找出整个jvm堆内存中的存活对象。
- 再次标记阶段(Remark),基于”Stop-the-World“机制,完成整个堆内存存活对象的标记。
- 清除阶段(Cleanup),首先算出所有活跃对象并释放一下*的Region块,然后处理Remembered Set,这两部分会暂停引用线程,然后并发重置空闲的Region块,并将它们放回空闲列表。
- 拷贝阶段(Copying),基于”Stop-the-World“机制,将存活对象复制到未使用的Region中