1. JVM ParNew + CMS垃圾回收器
1.1. ParNew
1.1.1. ParNew垃圾回收器简介
ParNew是用在新生代的垃圾回收器。即使有G1回收器,还有很多线上系统在用的ParNew回收器。
该回收器使用的是复制算法,采用多线程来进行回收,提高了回收效率。Serial垃圾回收器回收算法和ParNew一样,但是是单线程的。
1.1.2. 如何设置指定ParNew垃圾回收器
对于设置JVM参数,在Eclipse/IntelliJ IDEA中可以设置Debug JVM Arguments,使用“java -jar”命令启动时直接在后面跟上JVM参数即可。
部署到Tomcat时,可以在Tomcat的catalina.sh中设置Tomcat的JVM参数,使用Spring Boot也可以在启动时指定JVM参数。
启动时指定使用ParNew垃圾回收器参数,使用-XX:+UseParNewGC
,加入这个选项,JVM启动之后对新生代进行垃圾回收时会使用ParNew垃圾回收器。
1.1.3. ParNew垃圾回收器线程数量
部署系统的服务器都是多核CPU的,为了在垃圾回收的时候充分利用多核CPU的资源,指定使用ParNew垃圾回收器之后,会默认设置垃圾回收线程的数量,跟CPU的核数是一样的。
这个参数一般不用手动去调节,跟CPU核数一样的线程数量,是可以充分进行并行处理的。如果一定要自己调节ParNew的垃圾回收线程数量,使用-XX:ParallelGCThreads
参数即可,通过该参数来设置线程的数量。建议不要随意动这个参数。
1.2. CMS
1.2.1. CMS 垃圾回收的基本原理
老年代的垃圾回收器一般是CMS,采用的是标记清理算法,就是先用标记方法去标记出垃圾对象,然后把这些垃圾对象清理掉。
标记-清理算法,先通过追踪GC Roots
的方法,看对象是否被引用,如果有引用就是存活对象,否则就是垃圾对象。先将垃圾对象都标记出来,然后一次性把垃圾对象都回收掉。这种方法最大的问题是会造成很多内存碎片。
如果 CMS垃圾回收 时,一直Stop the World
,会导致系统长时间不能使用,无法响应请求,体验会很差。所以CMS垃圾回收器尽量同时执行垃圾回收线程和系统工作线程,来进行垃圾回收,提升效率。
1.2.2. CMS 垃圾回收的各个阶段介绍
为了提高使用体验,CMS 垃圾回收分为 4 个阶段,部分阶段可以不用Stop the World
,尽量减少系统暂停时间。详细介绍如下。
初始标记
CMS进行垃圾回收时,第一阶段,会先执行初始标记阶段,这个阶段会让系统的工作线程全部停止,进入Stop the World
状态。
虽然要Stop the World
暂停一切工作线程,但影响不大,因为速度很快,仅仅标记GC Roots直接引用的对象即可。(方法的局部变量和类的静态变量 是GC Roots。但是 类的实例变量不是GC Roots。)
并发标记
第二阶段,并发标记阶段,会让系统可以创建各种新对象,也可能部分存活对象失去引用,变成垃圾对象。
就是对老年代所有对象进行GC Roots追踪,是最耗时的。该阶段需要追踪所有对象是否从根源上被GC Roots引用(查看引用来源时候还有效),但这个最耗时的阶段是跟系统程序并发运行的,所以这个阶段不会对系统运行造成性能体验上的影响。
重新标记
第三个阶段,重新标记阶段,在第二阶段中会创建新的对象及老对象可能会变成垃圾对象,在该阶段会再次进入Stop the World
状态,对这部分对象进行重新标记。
重新标记阶段,速度会很快,第二阶段中变动过的对象会比较少,所以运行速度很快。
并发清理
第四阶段,并发清理阶段,该阶段就是让系统程序随意运行,然后同时清理掉标记为垃圾的对象即可。
该阶段很耗时,因为需要进行对象的清理,因为是跟系统程序并发运行的,所以不影响系统程序的执行。
CMS 垃圾回收机制性能分析
了解 CMS 垃圾回收机制之后,发现已经尽可能的进行性能优化了。
最耗时的其实就是对老年代全部对相关进行GC Roots追踪(标记出来到底哪些可以回收),然后对各种垃圾对象从内存里清理掉。
即第二阶段和第四阶段,这两个阶段都是和系统程序并发执行的,所以两个最耗时的阶段对性能影响不大。
只有 第一个阶段和第三个阶段是需要Stop the World
,这两个阶段都是简单的标记,速度非常的快,所以对系统运行响应也不大。
1.3. 总结
- JDK 8 及之前:
- 低延迟 → ParNew + CMS(已废弃,不建议新项目使用)。