目录
垃圾回收器
垃圾回收器的组合关系:
年轻代-Serial垃圾回收器:
老年代-SerialOld垃圾回收器:
年轻代-ParNew垃圾回收器:
老年代-CMS垃圾回收器:
年轻代-Parallel Scavenge:【JDK8默认】
老年代-Parallel Old垃圾回收算法[JDK8默认]
G1垃圾回收器:
总结:
垃圾回收器
- 系统中的大部分对象,都是创建出来之后很快就不再使用可以被回收,比如订单数据返回给用户之后就可以释放了。
- 老年代中会存放长期存活的对象,比如Spring的大部分Bean对象,在程序启动之后就不会被回收了
- 在虚拟机的默认设置中,新生代大小要远小于老年代大小
问题 :为什么分代GC算法要把堆分为年轻代和老年代?【基本是3:7 通过配置启动中的VM来执行查看】
原因:
垃圾回收器的组合关系:
垃圾回收器是垃圾回收算法的具体实现
由于垃圾回收器分为年轻代和老年代,除了G1之外的其他垃圾回收器必须成组合进行使用。
具体关系图如下:

年轻代-Serial垃圾回收器:
Serial是一种 单线程串行回收年轻代的垃圾回收器

回收年代和算法:年轻代、复制算法
优点:单CPU处理器下吞吐量非常出色
缺点:多CPU下吞吐量不如其他垃圾回收器,堆如果偏大会让用户线程处于长时间等待
适用场景:Java编写的客户端程序或者配置有限的场景。
老年代-SerialOld垃圾回收器:
SerialOld是Serial垃圾回收器的老年代版本,采用单线程串行回收
-XX:+UseSerialGC 新生代、老年代都是用串行回收器

年轻代-ParNew垃圾回收器:
ParNew垃圾回收器本质上是对Serial再多CPU下的优化,使用多线程进行垃圾回收
-XX:+UseparNewGC 新生代使用ParNew回收器,老年代使用串行回收器

老年代-CMS垃圾回收器:
CMS垃圾回收器 关注的是系统的暂停时间,允许用户线程和垃圾回收线程再某些步骤中同时执行,减少了用户线程的等待时间。
参数:XX:+UseConcMarkSweepGC

CMS执行步骤:
- 初始标记,用极短的时间标记处GC Roots能直接关联到的对象
- 并发标记,标记所有对象,用户线程不需要暂停【标记哪些对象需要回收,哪些不需要】
- 重新标记,由于并发标记阶段有些对象会发生了变化,存在错标、漏标等情况,需要重新标记【因为第二个阶段是并发执行的,所以需要对用户线程发生的变化进行处理】
- 并发编程,清理死亡的对象,用户线程不需要暂停。
CMS垃圾回收器存在的问题:
- CMS使用了标记清除算法,在垃圾回收结束之后会出现大量的内存碎片,CMS会在FUll GC时进行碎片整理。这样会导致用户线程暂停,可以使用-XX:CMSFullGCsBeforeCompaction=N 参数 (默认为0) 调整N次Full GC之后再整理。
- 无法处理在并发清理过程中产生的“浮动垃圾”,不能做到完全的垃圾回收
- 如果老年代内存不足无法分配对象,CMS就会退化成Serial Old单线程回收老年代。【老年代内存不足是因为步骤二中无法处理并发清理过程中的“浮动垃圾”,并且这个时间就会特别长】
年轻代-Parallel Scavenge:【JDK8默认】
Parallel Scavenge是JDK8默认的年轻代垃圾回收器,多线程并行回收,关 注的是系统的吞吐量。具备自动调整堆内存的大小的特点、 可以手动设置最大垃圾回收时间和吞吐量、然后PS根据你设置的这两个参数来自动调整内存大小
吞吐量的计算公式: 吞吐量 = CPU在用户应用程序运行的时间 / (CPU在用户应用程序运行的时间 + CPU垃圾回收的时间)

参数:

老年代-Parallel Old垃圾回收算法[JDK8默认]

G1垃圾回收器:
JDK9之后默认的垃圾回收器是G1(Garbage First)垃圾回收器
Parallel Scavenge关注吞吐量,允许用户设置最大暂停时间,但是会减少年轻代可用空间的大小
CMS关注暂停时间,但是吞吐量方面会下降
而G1设计目标就是将上述两种垃圾回收器的优点融合:
- 支持巨大的堆空间回收,并具有较高的吞吐量。
- 支持多CPu并行垃圾回收
- 允许用户设置最大暂停时间
G1垃圾回收器有两种方式:
- 年轻代回收
- 混合回收
年轻代回收:
回收Eden去和Survivor区中不用的对象。会导致STW,G1中可以通过参数-XX:MaxGCpauseMillis=n (默认是200)设置每次垃圾回收是的最大暂停时间毫秒数,G1垃圾回收器会尽可能地保证暂停的时间
执行流程:
- 新创建的对象会存放在Eden区。当G1判断年轻代区不足(max默认为60%),无法分配对象时需要回收时会执行Young GC。
- 标记出Eden和Survivor区域中的存活对象
- 根据最大的暂停时间来选择某写区域将存活对象复制到一个新的Survivor区中(年龄+1),清空这些区域。
- G1在进行Young GC的过程中回去记录每次垃圾回收时灭个Eden区和Survivor区的平均耗时,以作为下次回收时的参考依据。这样就可以根据配置的最大暂停时间计算出本次回收最多能回收多少个Region区域了,比如-XX:MaxGCPauseMillis=n(默认为200),每个Region回收耗时40ms,那么这次回收最多只能回收4个Region。
- 后续的Young GC时与之前相同,只不过Survivor区中存活对象会被搬运到另一个Survivor区。
- 当某个存活对象的年龄到达阈值(默认15),将被放入老年代
- 部分对象如果大小超过Region的一半,会直接放入老年代,这类老年代被称为Humongous区,比如对内存是4G,每个Region时2M,只要一个大对象超过了1M就会被放入Humongous区,如果对象过大会横跨多个Region。
- 多次回收之后,会出现很多Old老年代区,此时总堆占有率达到阈值时(-XX:IniniatingHeapOccupancuPercent 默认45%)会触发混合回收MixedGC。回收所有年轻代和部分老年代的对象以及大对象区。采用复制算法来完成
混合回收执行流程:
- 混合回收分为:初始标记、并发标记、最终标记、并发清理
- G1对老年代的清理会选择存活度最低的区域来进行回收,这样可以保证回收效率最高,这也是G1名称的由来。

- G1对老年代的清理会选择存活度最低的区域来进行回收,这样可以保证回收效率最高,这也是G1(Garbage First)名称的由来
- 最后清理阶段使用复制算法,不会产生内存碎片。

FULL GC:
注意:如果清理过程中发现没有足够的空Region存放转移的对象,会出现Full GC 单线程执行标记-整理算法,此时会导致用户线程的暂停。所以尽量保证应该用的堆内存有一定多余的空间。

G1-Garbage First垃圾回收器:
参数1:-XX:+UseG1GC 打开G1的开关,JDK9之后默认不需要打开
参数2:-XX:MaxGCPauseMillis=毫秒值最大暂停的时间

总结:
垃圾回收器的组合关系虽然很多,但是针对几个特定的版本,比较好的组合选择如下:
- JDK8之前:
- ParNew + CMS (关注暂停时间)、Parallel Scavenge + Parallel Old(关注吞吐量)、G1(JDK8之前不建议,较大堆并且关注暂停时间)
- JDK9之后:
- G1
- Java中有哪几块内存需要进行垃圾回收?
- 对于线程不共享的部分:程序计数器、java虚拟机栈、本地方法栈,跟随线程的生命周期随着线程回收而回收
- 对于线程共享的部分:方法区一般不需要回收,堆由垃圾回收器进行回收
- 有哪几种常见的引用类型?
- 强引用,最常见的引用方式,由可达性分析算法来判断【通过GCRoot可以找到的就是强引用】
- 软引用,对象在没有强引用的情况下,内存不足时会回收
- 弱引用,对象在没有强引用情况下,会直接回收
- 虚引用,通过虚引用知道对象被回收了
- 终结器引用,对象回收时可以自救,不建议使用
- 有哪几种常见的垃圾回收算法?
- 标记-清除算法:标记之后再清除,容易产生内存碎片
- 复制算法:从一块区域复制到另一块区域,容易造成只能使用一部分内存
- 标记-整理算法:标记之后将存活的对象推到一边对象会移动,效率不高
- 分代GC:将内存区域划分为年轻代、幸存者区、老年代进行回收,可以使用多种回收算法
- 常见的垃圾回收器有哪些?
- Serial + Serial Old:单线程回收,主要适用于单核CPU场
- parNew + CMS:暂停时间较短,适用于与用户交互的部分
- Parallel Scavenge + parallel Old:吞吐量高,适用于后台进行大量数据操作
- G1:适用于较大的堆,具有可控的暂停时间