常见垃圾收集器
- 垃圾收集器介绍
- 垃圾收集器使用命令及默认值
- 串行垃圾收集器
- 并行垃圾收集器
- CMS(并发)垃圾收集器
- G1收集器(Garbage-First Collector)
- 面试题:为何新生代和老年代采取的算法不一样?
垃圾收集器介绍
GC算法(复制/标清/标整/分代收集)是内存回收的方法论,垃圾收集器就是算法落地实现(即复制,标记清除,标记压缩整理和分代收集四种算法的实现类)。可以将垃圾收集器分为如下几类:
- 串行垃圾收集器
- 并行垃圾收集器
- CMS(并发垃圾收集器)
- G1收集器
接下来介绍收集器在新生代(Young generation)和老年代(Tenured generation)之间的组合关系
如果两个收集器之间存在连线,就说明它们可以搭配使用。它们说在的区域则表示这个收集器属于新生代收集器还是老年代收集器。其中Serial(串行)、Parallel(并行)
- JDK8中 默认使用 Parallel Scavenge GC + Parallel Old GC
- JDK9中 默认使用 G1垃圾收集器
- JDK14移除了 CMS GC
- JDK14中:弃用Parallel Scavenge和Serial old GC组合
垃圾收集器使用命令及默认值
那我们怎么去使用限定使用这些垃圾收集器呢,可以使用以下命令:
串行垃圾收集器
Serial和Serial Old串行垃圾收集器,是指使用单线程进行垃圾回收,堆内存较小,适合个人电脑
- Serial作用于新生代,采用复制算法
- Serial Old作用于老年代,采用标记整理算法
垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成
并行垃圾收集器
Parallel New、Parallel Scavenge和Parallel Old属于并行垃圾收集器
- Parallel New作用于新生代,采用复制算法
- Parallel Scavenge收集器类似Parallel New,作用于新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。
- Parallel Old作用于老年代,采用标记整理算法
垃圾回收时,使用多线程进行垃圾回收,并且java应用中的所有线程都要暂停(STW),等待垃圾回收的完成
JDK1.8的垃圾收集器:新生代是使用Parallel Scavenge收集器,老年代是使用Parallel Old收集器
Parallel Scavenge 和 ParNew 收集器对比?
- Parallel Scavenge 和 ParNew 收集器不同, Parallel Scavenge 收集器的目标是达到一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器
- 自适应调剂策略也是 Parallel Scavenge 与 ParNew 的一个重要区别(自适应调节策略:虚拟机会根据当前运行情况,动态调整年轻代的大小、Eden和Survivor 的比例、晋升老年代的对象年龄参数)
CMS(并发)垃圾收集器
CMS(Concurrent-Mark-Sweep),是一款并发的、使用标记-清除算法的垃圾回收器,该回收器是针对老年垃圾回收的。
-
CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟) 就越适合与用户交互的程序,良好的响应速度能提升用户体验
-
CMS 的垃圾收集算法采用 标记-清除算法,并且也会 “Stop-the-world”
-
初始标记(Initial-Mark) 阶段: 在这个阶段中,程序中所有的工作线程都将会因为 “Stop-the-World” 机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出 GC Roots 能直接关联到的对象。
-
并发标记(Concurrent-Mark) 阶段: 从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
-
重新标记(Remark) 阶段: 由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有工作线程
-
并发清除(Concurrent-Sweep) 阶段: 此阶段清理删除掉标记阶段判断已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以这个阶段也可以与用户线程同时并发的
CMS垃圾收集器虽然减少了暂停应用程序的运行时间,但是它还是存在着内存碎片问题,于是为了去除内存碎片问题,同时有保留CMS垃圾收集器低暂停时间的优点,
缺点:
- CMS收集器对CPU资源很敏感。它虽然不会导致用户线程停顿,但也会因为占用一部分线程导致应用程序变慢。
- CMS是一款基于“标记-清除”算法实现的收集器,这意味着收集结束时会有大量空间碎片产生。空间 碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找 到足够大的连续空间来分配当前对象,所以会提前导致Full GC的到来。
- CMS无法处理浮动垃圾(在清除阶段用户线程还在运行,产生的垃圾),必须等到下次GC时才能清理,而且不能等到老年代满了后再清理,因为再清理过程中用户线程还在产生对象,所以要预留一定内存,提前开启垃圾清理。如果浮动垃圾导致内存不足时候,出现“Concurrent Mode Failure”,出现此错误时就会切换到SerialOld收集模式
G1收集器(Garbage-First Collector)
JDK9之后默认使用G1
G1收集器它将堆内存划分为多个大小相等的区域Region,使用不同的Region 来标识 Eden、幸存者0区、幸存者1区、老年代等,并且使用多线程进行垃圾回收。能与应用线程并发执行
- G1收集器的目标是在保证低停顿时间的同时,尽可能地高效利用可用的系统资源。
- G1收集器是一个有整理内存过程的垃圾收集器,不会产生内存碎片
- G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间
-XX:MaxGCPauseMillis: 设置期望达到的最大 GC 停顿时间指标( JVM 会尽力实现,但不保证达到)。默认值是 200 ms
G1 回收器的特点:
- 并行与并发
并行性: G1在回收期间,可以有多个 GC 线程同时工作,有效利用多核计算能力。此时用户线程 STW
并发性: G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况
- 分代收集
从分代上看,G1依然属于分代型垃圾回收器,它会区分年轻代和年老代,年轻代依然有Eden区和Survivor 区。但是从堆的结构上看,它不要求整个 Eden 区、年轻代或者老年代都是连续的,也不再坚持固定大小和固定数量。其中的Humongous区域专门为大对象准备的
将堆空间分为若干个区域(Region),G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可,每个分区也不会固定地为某个代服务,可以按需在年轻代和老年代之间切换。Region大小范围在1MB~32MB,最多能设置2048个区域。
- 空间整合
G1 将内存划分为一个个 Region。内存的回收是以 region 作为基本单位的。 Region 之间是复制算法,但整体上可看作是标记-压缩(Mark-Compact) 算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触下一次 GC。尤其是当 Java 堆非常大的时候,G1 的优势更加明显
G1设置humongous的原因?
对于堆中的大对象,默认会直接分配到老年代,但是如果它是一个短期存在的大对象,就会对垃圾收集器产生负面影响。为了解决这一个问题,G1划分出了一个Humongous区,它专门用来存放大对象,如果一个H区装不下大对象,那么会找寻连续的H区来存储,为了能找到连续的H区,有时候不得不启动Full GC,G1的大多数行为都把H区作为老年代的一部分来对待。
面试题:为何新生代和老年代采取的算法不一样?
新生代所采取的算法是标记-复制算法(复制算法),老年代采用的是标记-清除算法和标记–整理算法。
最早出现的垃圾收集算法是“标记-清除”算法,但是他主要有两个缺点:
- 执行效率不稳定,如果堆中大部分对象需要回收,那么需要进行大量标记和清除动作,执行效率会随着对象数量增长而降低
- 内存空间的碎片化问题
对于新生代而言,因为需要面对的大部分对象都是可回收的对象,所以标记-复制算法应运而生。
因为该算法只需要复制的只是占少数的存活对象,而且分配内存是不用考虑有空间碎片的情况。
但是,复制算法在对象存活率较高时,需要较多的复制操作,效率会降低,尤其是该算法会浪费掉一部分内存,所以不适合老年代。这个时候“标记-清除算法”以及“标记-整理算法”便可以发挥他们的作用了。