# JVM GC 回顾

因为历史原因,Java GC已经有几代的实现,本文回顾java 11 ZGC之前的GC实现。

# 什么是GC(Garbage Collection)

从名字上看,GC是从内存中寻找并删除垃圾的过程,但实际上GC会追踪JVM heap中每一个对象,并删掉无用对象回收内存。

简单来讲,GC由Mark和Sweep两个简单步骤实现。

  • Mark 这个步骤垃圾收集器标记一块内存是否在被使用
  • Sweep 这个步骤移除了被标记的对象

jvm这样实现GC有着哪些优点和缺点呢?

优点:

  • 不需要手动分配内存/取消分配内存,因为内存空间就是由GC自动管理的
  • 不会有处理野指针(Dangling Pointer)的开销

在计算机编程领域中,迷途指针,或称悬空指针、野指针,指的是不指向任何合法的对象的指针。----wikipedia

  • 可自动管理内存泄露(GC本身并非内存泄露的完整解决方案,但是它可以解决大部分问题)

在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。----wikipedia

缺点:

  • 由于JVM必须持续追踪对象的创建/删除,因此相较于程序本身需要更多的CPU性能。这可能会影响需要极大内存的程序的性能。
  • 程序员没有办法去控制“释放不再需要的对象”这部分的cpu时间调度。
  • 使用某些GC实现也许会导致程序意外崩溃。
  • 自动化的内存管理不如手动管理有效率(毕竟需要算法去判断)

# GC实现

在java11之前,JVM曾有四种GC实现:

  • Serial Garbage Collector
  • Parallel Garbage Collector
  • CMS Garbage Collector
  • G1 Garbage Collector

# Serial GC

Serial GC是最简单的GC实现,因为它基本上可以在单线程 上工作。但是这个GC实现会在GC运行时冻结所有的应用程序线程。因此,它不适合在服务器等多线程应用中使用。

Serial GC对大多有大块暂停时间的应用(比如各种客户端) 比较适合。要启用Serial GC,需要下列参数:

java -XX:+UseSerialGC -jar Application.java

# Parallel GC

Parallel GC是JVM默认的的GC,也被称为Throughput GC。不像串行垃圾收集器,Parallel使用多线程来管理heap空间。但是它在执行GC的过程中依然会暂停其他应用程序线程。

如果使用这个GC,我们可以详细的指定GC线程数量(threads)和暂停时间(pause time),吞吐量(throughput)和占用空间(footprint)(堆大小)

  • 垃圾收集器线程的数量可以由命令行-XX:ParallelGCThreads=<N>控制

  • 最大暂停时间(两次GC间隔时间,毫秒计算)可由命令行 -XX:MaxGCPauseMillis=<N>控制

  • 最大吞吐量(衡量花费在垃圾收集上的时间和之外的时间)由命令行XX:GCTimeRatio=<N>控制

  • 最大堆占用空间(程序运行时所需的最大堆内存量)由命令行-Xmx<N>控制

要开启Parallel GC,使用下列参数:

java -XX:+UseParallelGC -jar Application.java

# CMS GC

Concurrent Mark Sweep(CMS)GC使用多个垃圾回收线程做垃圾回收。它是为更短的垃圾回收暂停时间而设计,并且程序运行时可以和垃圾回收器共享处理器资源。

简而言之,采用此类GC的程序平均响应更慢,但是不会在执行GC时暂停响应。

需要注意的是,由于GC是并发的,如果并发操作正在运行,这时显式调用像System.gc()的垃圾回收,会导致并发模式失败(Concurrent Mode Failure)。

如果超过98%的时间用于CMS GC且少于2%的时间回收堆,CMS收集器将会抛出OutOfMemoryError。如有必要,可以通过命令行XX:-UseGCOverheadLimit来禁用这个功能。

要启用CMS垃圾收集器,可以使用下列命令:

java -XX:+UseParNewGC -jar Application.java

# G1 GC

G1(Garbage First) GC被设计用于面向运行在多核处理器上且有极大内存空间的程序。G1是取代CMS的下一代收集器,因为它有更高的性能效率。

不像其他收集器,G1收集器会将堆划分为一组大小相等的堆区域,每个堆区域都是一块连续的虚拟内存。当执行GC时,G1进入一个并发全局标记阶段(阶段1:Marking),以确定整个堆中对象的存活性。

Mark阶段完成后,G1会了解哪块区域大部分应该被清空。G1首先回收这些区域的内存,这样可以达到快速回收大量可用内存的目的(阶段2:Sweeping)。这就是为什么这种GC方法被称为Garbage-First。

开启G1 GC,可以使用下面的参数:

java -XX:+UseG1GC -jar Application.java