浅谈JVM GC算法、垃圾收集器及如何选择

引言

GC算法(引用计数/复制/标记清除/标记整理)是内存回收的方法论,垃圾收集器就是算法的落地实现。目前还没有完美的收集器,只是针对具体应用最合适的收集器,进行分代收集。

四大GC算法

引用计数:每次对对象赋值时均要维护引用计数器,且计数器本身也有一定的消耗,较难处理循环引用,JVM的实现一般不会采用这种方式。

复制算法:复制–>清空–>互换 --如此15次后如果还有对象存活则进入老年代(JVM参数MaxTenuringThreshold默认为15)
1 复制(eden、survivorFrom复制到survivorTo,年龄+1)
当Eden区满时会触发第一次GC,将活着的对象拷贝到SurvivorFrom区;当Eden再次插法GC时会扫描Eden区和SurvivorFrom区,对这两个区域进行垃圾回收,经过这次回收还存活的对象将被拷贝到To区域(如果已经有对象的年龄已经达到了老年标准则直接复制到老年代),同时把对象的年龄+1;
2 清空eden 和survivorFrom区
3 互换survivorFrom与survivorTo(谁空谁是To)

优点:没有内存碎片 ; 缺点:有点浪费内存空间(survivorTo)且大对象复制耗时

标记清除:先标记出要回收的对象,然后统一回收这些对象。
优点:对象的标记没有大面积复制,节约内存空间
缺点: 产生内存碎片

标记整理:1. 标记 2. 压缩,再次扫描并往一端移动存活对象
有点:没有内存碎片 缺点:增加移动对象的成本

总结:新生代收集:复制; 老年代收集:标记清除&标记整理。

四大垃圾收集方式

1 串行垃圾回收器(Serial):只使用一个GC线程进行回收,会暂停所有的用户线程,不适合服务器环境。
2 并行垃圾回收器(Parallel):多个GC线程进行垃圾回收,也会暂停所有用户线程,适用与和前台交互不强的场景,如科学计算/大数据处理等若交互场景。
3 并发垃圾回收器(CMS):用户线程和GC线程同时执行(并不一定时并行,可交替执行),不需要停顿用户线程,互联网公司使用较多,适用于对相应时间有要求的场景。
在这里插入图片描述
4 G1垃圾回收器:将内存分割成不同的区域,然后并发的对其进行垃圾回收。

七大垃圾收集器

首先如何查看环境默认的垃圾收集器:
java -XX:+PrintCommandLineFlags -version
在这里插入图片描述
七种垃圾收集器:
在这里插入图片描述
在这里插入图片描述
在GC日志中出现的一些参数与垃圾收集器和分代的关系:
DefNew Serial 新生代
Tenured SerialOld 老年代
ParNew ParallelNew 新生代
PSYongGen Parallel Scavenge 新生代
ParOld ParallelOld 老年代

1、Serial串行收集器
在这里插入图片描述
Serial时最古老的收集器,最稳定以及高效,由于只使用单个线程进行垃圾收集可能产生较长时间的停顿。了解即可,工作中不会使用。
JVM参数: -XX:+UseSerialGC
开启后会使用:Serial(yong区)+ SerialOld(old区)
表示新生代和老年代都会使用串行收集器,新生代使用复制算法,老年代使用复制整理算法。
演示:JVM参数配置:
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC

public class GCDemo {
	public static void main(String[] args) {
		try {
			byte[] a = new byte[20 * 1024 * 1024];
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
}

在这里插入图片描述
2、PraNew并行收集器
在这里插入图片描述
ParNew收集器是Serial收集器的新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和Serial收集器一样,同样需要暂停用户线程,他是很多java虚拟机运行在server模式下新生代的默认垃圾收集器。

JVM参数:-XX:+UseParNewGC 启用ParNew只影响新生代的收集,不影响老年代。 -XX:ParallelGCThreads 设置并行GC线程数,默认开启和CPU数目相同的线程数
开启上述参数后会使用:ParNew(Yong) + SerialOld(old) 此搭配java已废弃
新生代算法:复制, 老年代算法:标记整理;

在这里插入图片描述
3、Parallel Scavenge并行收集器
在这里插入图片描述
Parallel Scavenge收集器类似与ParNew,新生代–复制算法,是一个并行多线程垃圾收集器,俗称吞吐量有限收集器。

主要特点:
可控制的吞吐量:用户线程运行时间/(GC线程运行时间 + 用户线程运行时间),比如:程序运行100分钟,垃圾收集时间1分钟,则吞吐量为99%。高吞吐量意味者高效的使用CPU,多用于后台计算而不需要太多交互的任务。
自适应调节策略也是ParNew与ParallelScavenge的一个重要区别,虚拟机会根据当前系统运行的情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间(-XX:+MaxGCPauseMillis)或最大的吞吐量。
JVM参数:-XX:+UseParallelGC(此参数同时激活-XX:+UseParallelOldGC–老年代)
开启此参数:新生代:复制; 老年代:标记整理; -XX:+ParallelGCThreads = N,表示开启多少个GC线程,cpu>8 N=5/8; cpu <8 N=实际个数
在这里插入图片描述
4、Parallel Old收集器
Parallel Old收集器是Parallel Scavenge的老年代版本,使用标记整理算法,在JDK1.6才开始提供。在JDK1.6之前Parallel Scavenge只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量。

对系统吞吐量要求比较高,JDK1.8后可有限考虑新生代Parallel Scavenge +老年代Parallel Old的搭配策略。
JVM参数:-XX:+UseParallelOldGC,开启此参数会激活-XX:+UseParallelGC.

5、CMS(Concurrent Mark Sweep:并发标记清除收集器)
是一种以获取最短回收停顿时间为目的的收集器。适用于互联网站或者B/S系统的服务器上,此类引用重视响应速度,希望停顿时间短。CMS非常适合对内存大,CPU核数多的服务器应用,也是G1出现之前大型应用的首选收集器。
在这里插入图片描述
共四步:初始标记–>并发标记—>重新标记—>并发消除
初始标记(CMS initail mark):只是标记GC Roots能直接关联的对象,速度快,仍需要暂停用户线程;
并发标记(CMS concurrent mark) 和用户线程一起:执行GC Roots跟踪的过程,不会暂停用户线程,主要标记过程,标记全部对象;
重新标记(CMS remark):由于并发标记时,用户线程仍然运行,因此在清理前再次修正,需暂停用户线程;
并发清除(CMS concurrent sweep) 和用户线程一起:清除GC Roots不可达对象,基于标记的结果直接清除,不需要暂停用户线程。

由于耗时最长的并发标记和并发清除过程中不会暂停用户线程,总体上CMS收集器的内存回收和用户线程是一起并发的执行。

优势:并发收集停顿低,并发指GC线程与用户线程一起执行。
缺点:并发执行对CPU压力大,采用标记清除算法会导致内存碎片。

JVM参数:-XX:+UseConcMarkSweepGC,开启此参数默认激活-XX:+UseParNewGC,使用ParNew(yong)+CMS(Old)+SerialOld收集器的组合,Serial Old将作为CMS出错的后备收集器。

由于标记清除无法整理内存碎片,老年代迟早被逐步耗尽,如果CMS回收失败,将会触发担保机制,Serial Old将会以STW的方式进行一次GC,从而造成较长时间停顿。CMS提供了-XX:CMSFullGCBeForeCompaction(默认为0,即每次都进行内存整理)来指定多少次CMS收集之后进行依次压缩的Full GC.
在这里插入图片描述
6、Serial Old收集器 — java8已废弃,了解
是Serial收集的老年代版本,单线程,标记整理。
在server模式下主要有两个用途:
1.在JDK1.5之前与新生代的Parallel Scavenge搭配使用
2.作为老年代的CMS收集器的后备垃圾收集方案。

如何选择垃圾收集器

1、单CPU或者小内存,单机程序 — -XX:+UseSerialGC
2、多CPU,需要大吞吐量,如后台计算型应用
-XX:+UseParallelGC + -XX:+UseParallelOldGC
3、多CPU,追求低停顿时间,快速响应如互联网应用
-XX:+UseParNewGC + -XX:+UseConcMarkSweepGC

你可能感兴趣的:(jvm,GC)