Java 虚拟机提供了多种垃圾回收器,每种回收器有其特定的用途和优势。以下是常见的垃圾回收器:
JVM 在进行 GC 时,并非每次都对堆内存(新生代、老年代;方法区)区域一起回收的,大部分时候回收的都是指新生代。
针对 HotSpot VM 的实现,它里面的 GC 按照回收区域又分为两大类:部分收集(Partial GC
),整堆收集(Full GC
)。
Minor GC
/ Young GC
):只是新生代的垃圾收集。Major GC
/ Old GC
):只是老年代的垃圾收集。目前,只有 CMS GC 会有单独收集老年代的行为。很多时候 Major GC 会和 Full GC 混合使用,需要具体分辨是老年代回收还是整堆回收。Mixed GC
):收集整个新生代以及部分老年代的垃圾收集。目前只有 G1 GC 会有这种行为。-XX:+UseSerialGC
-XX:+UseSerialOldGC
Serial 是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
1.5
以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。-XX:+UseParallelGC
(新生代)-XX:+UseParallelOldGC
(老年代)Parallel 收集器其实就是 Serial 收集器的 多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器类似。默认的收集线程数 跟 CPU 核数 相同,当然也可以用参数(-XX:ParallelGCThreads
)指定收集线程数,但是一般不推荐修改。
Parallel Scavenge 收集器关注点是 吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的 停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
新生代采用 复制 算法,老年代采用 标记-整理 算法。
Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本。使用 多线程 和 标记-整理 算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器(JDK 8 默认的新生代和老年代收集器)。
-XX:+UseParNewGC
(新生代)ParNew 收集器其实跟 Parallel 收集器很类似,区别主要在于它可以和 CMS 收集器配合使用。
新生代采用 复制 算法。
-XX:+UseConcMarkSweepGC
(老年代)CMS(Concurrent Mark Sweep
),Mark Sweep
指的是 标记-清除 算法。
CMS 收集器是一种以获取 最短回收停顿时间 为目标的收集器。它非常符合在注重用户体验的应用上使用,它是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
CMS 主要优点:并发收集、低停顿。但是它有下面几个明显的缺点:
-XX:+UseCMSCompactAtFullCollection
可以让 JVM 在执行完标记清除后再做整理,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。G1(Garbage-First
)是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。
堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。G1 把堆划分成多个大小相等的 独立区域(Region
),新生代和老年代不再物理隔离。
通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
每个 Region 都有一个 Remembered Set
,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set
,在做可达性分析的时候就可以避免全堆扫描。
G1 收集器的运作大致可划分为以下几个步骤:
Initial Marking
):仅仅只是标记一下 GC Roots 能直接关联到的对象。Concurrent Marking
):从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。Final Marking
):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB(Snapshot At The Beginning,原始快照)记录。Live Data Counting and Evacuation
):负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。它具备以下特点:
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
从 JDK 9 开始,G1 垃圾收集器成为了默认的垃圾收集器。
对于 Minor GC,其触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:
System.gc()
只是建议虚拟机执行 Full GC,但是虚拟机不一定真正去执行。不建议使用这种方式,而是让虚拟机管理内存。-Xmn
虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold
调大对象进入老年代的年龄,让对象在新生代多存活一段时间。1.7
及以前的永久代空间不足(1.7
之后元空间不足)。在 JDK 1.7
及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError
。为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。