一、常见的垃圾收集器有串行垃圾回收器(Serial)、并行垃圾回收器(Parallel)、并发清除回收器(CMS)、G1回收器。
1、串行垃圾回收器。
为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,所以不适合服务器环境。
堆内存中新生代垃圾回收器(Serial):
串行收集器是最古老,最稳定以及效率高的收集器,只使用一个线程去回收,但其在进项垃圾收集过程中可能会产生较长的停顿(Stop-The-World)状态。虽然在收集垃圾的过程找那个需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。
堆内存中老年代垃圾回收器(Serial Old):
Serial old是Serial垃圾收集器老年代版本,它同样是单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的老年代垃圾收集器,在老年代中,也充当CMS收集器的后备垃圾收集方案。(GC单线程)
2、并行垃圾回收器
多个垃圾回收器并行工作,此时用户线程是暂时的,适用于科学计算/大数据处理等弱交互场景。
堆内存中新生代垃圾回收器(ParNew、Parallel Scavenge):
ParNew收集器其实就是Serial收集器新生代的并行多线程版本,最常见的应用场景是配合老年代CMS GC工作,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。(GC多线程)
JVM配置:-XX:+UseParNewGC 使用ParNew收集器
Parallel Scavenge收集器类似ParNew也是一个新生代垃圾收集器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。(GC多线程)
JVM配置:-XX:+UseParallelGC 使用Parallel Scavenge收集器
堆内存中老年代垃圾回收器(Parallel Old):
Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,Parallel Old收集器在JDK1.6才开始提供。
在JDK1.6之前,新生代使用Parallel Scavenge收集器只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体吞吐量。
JDK1.8后考虑新生代Parallel Scavenge和老年代Parallel Old收集器的搭配策略。
JVM配置:-XX:+UseParallelOldGC 使用Parallel Old收集器
3、并发清除回收器
CMS(Concurrent Mark Sweep:并发标记清除) 是一种以获取最短回收停顿时间为目标的收集器。
CMS非常适合堆内存大、CPU核数多的服务器端应用,也是G1出现之前大型应用的首选收集器。
用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程,互联网公司多用它,适用对响应时间有要求的场景。 主要在老年代回收。
初始标记(Initial Mark):只是标记一下GC Roots能关联的对象,速度很快,仍然需要暂停所有工作线程。
并发标记(Concurrent Mark):进项GC Roots跟踪过程,和用户线程一起执行,不需要暂停工作线程,主要标记过程、标记全部对象。
重新标记(Remark):为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有用户线程。
并发清除(Concurrent Sweep):清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程,基于标记结果,直接清除对象。
CMS优点:并发收集停顿低。缺点:并发执行对CPU资源消耗大,标记清除算法会产生碎片。
JVM配置:-XX:+UseConcMarkSweepGC 使用CMS收集器
4、G1垃圾回收器
G1(Garbage-First)垃圾回收器将堆内存分割成不同的区域,然后并发的对其进行垃圾回收。G1收集器的设计目标是取代CMS收集器,它同CMS相比,不会产生大量内存碎片,并可以添加预测机制,用户可以指定期望停顿时间(可通过配置-XX:MaxGCPauseMills=n最大停顿时间)。
特点:
- G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW。
- G1整体采用标记-整理算法,局部通过复制算法,不会产生大量内存碎片。
- 宏观上看G1之中不再区分年轻代和老年代。把内存划分成多个独立的子区域,可以近似理解为一个围棋的棋盘。
- G1收集器里面将整个内存区都混合在一起了,但其本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但他们不再是物理隔离了,而是一部分Region的集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。
- G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代和老年代的区别,也不需要完全独立的Survivor堆做复制准备,G1只有逻辑上的分代概念,或者说每个分区都可以随G1的运作在不同代之间前后切换。
区域化内存划分Region,整体变为一系列不连续的内存区域,避免了全内存区的GC操作。核心思想是将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小,在堆的使用上,G1并不要求对象的存储一定在物理上连续的,只要逻辑上连续即可,每个分区也不会固定的为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数配置-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
大小范围在1MB~32MB,最多能设置2048个大小相同的子区域,也即能够支持的最大内存为:32MB*2048 = 65536MB ≈ 64G内存
收集演示:
这些Region的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活的对象拷贝到老年代或者Survivor空间。
这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另一个区域,完成了清理工作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样就不会有CMS的内存碎片问题了。
G1收集器运行过程
- 初始标记:只是标记GC Roots能直接关联的对象
- 并发标记:进项GC Roots跟踪的过程
- 最终标记:修正并发标记期间,因程序运行而导致标记发生变化的那一部分对象
- 筛选回收:根据时间来进项价值最大化的回收
JVM配置:-XX:+UseG1GC 使用G1收集器
二、如何选择垃圾回收器
组合选择:
- 单CPU或小内存,单机程序
-XX:+UseSerialGC
- 多CPU,需要最大吞吐量,如后台计算型应用
-XX:+UseParallelGC或者
-XX:+UseParallelOldGC
- 多CPU,追求低停顿时间,需快速响应如互联网应用
-XX:+UseConcMarkSweepGC
-XX:+ParNewGC