深度解析(图文)JVM垃圾收集器(二)

通过上篇文章,我们知道在JVM中对象的分配、哪些对象是需要回收以及垃圾回收器中用到的算法,这篇文件主要讲解在JVM中所有的垃圾回收器以及各个垃圾回收器是如何回收

一:垃圾收集器搭配以及概念

 

深度解析(图文)JVM垃圾收集器(二)

如图所示,按照对空间的划分垃圾收集器可分为年轻代和老年代垃圾收集器

年轻代收集器:Serial、ParNew、Parallel Scavenge

老年代收集器:CMS、Serial Old、Parallel Old

整堆收集器:G1

并行收集器:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态

并发收集器:指用户线程与垃圾收集线程同时工作,用户程序在继续运行

吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%

 

二:垃圾收集器分类

1、Serial 收集器(复制算法)

    深度解析(图文)JVM垃圾收集器(二)

缺点:它是一个单线程收集器,只会使用一个CPU或一条收集线程去完成垃圾收集工作,意味着它在进行垃圾收集时,必须暂停其他所有的工作线程,直至Serial收集器收集结束为止(“Stop The World”)

优点:简单高效,对于限定单个CPU的环境来说,Serial收集器没有线程交互(切换)开销,可以获得最高的单线程收集效率

2、parNew 收集器(复制算法)

深度解析(图文)JVM垃圾收集器(二)

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程外其他行为均和Serial收集器一样,包括控制参数、收集算法(复制算法)、Stop The World、对象分配规则、回收策略等,它是许多运行在Server模式下的虚拟机中首选的新生代收集器

缺点:虽然在Serial版本上使用的多线程的使用了多线程回收,但是也会存在stw, 在单核CPU的环境中,由于回收线程的来回切换开销,所以效率不比Serial高

优点:在多核CPU的环境中,由于多线程回收,效率提升很高,它默认开启的收集线程数与CPU的数量相同,在CPU非常多的情况下可使用-XX:ParallerGCThreads参数设置垃圾收集的线程数。

3、parallel scaverge(复制算法)

深度解析(图文)JVM垃圾收集器(二)

Parallel Scavenge收集器看上去和parNew一样,与ParNew不同的是,Parallel Scavenge的目标是达到一个可控的吞吐量,吞吐量=程序运行时间/(程序运行时间+GC时间),如程序运行了99s,GC耗时1s,吞吐量=99/(99+1)=99%,Parallel Scavenge收集器也经常称为“吞吐量优先”收集器,目标是控制吞吐量和自适应调节。高吞吐量为目标,即减少垃圾收集时间,让用户代码获得更长的运行时间,提供了两个参数用于精确控制吞吐量

-XX:MaxGCPauseMillis:最大垃圾回收停顿时间,单位为ms。这个参数的原理是空间换时间,虚拟机将尽力保证每次MinorGC耗时不超过所设时长,但并不是该时间越小越好,因为GC耗时缩短是用调小年轻代大小获取的,回收500m的对象肯定要比回收2000m的对象耗时更短,但是回收频率也大大增大了,吞吐量也随之下去了。使用该参数的理论效果:MaxGCPauseMillis越小,单次MinorGC的时间越短,MinorGC次数增多,吞吐量降低。 

-XX:GCTimeRatio:垃圾收集时间与总时间占比,GC耗时的计算公式为1/(1+n),n为GCTimeRatio,因此,GCTimeRatio的实际用途是直接指定吞吐量。GCTimeRatio的默认值为99,因此,GC耗时的占比应为1/(1+99)=1%。使用参数的理论效果:GCTimeRatio越大,吞吐量越大,GC的总耗时越小。有可能导致单次MinorGC耗时变长。适用于高运算场景 

-XX:+UseAdaptiveSizePolicy:是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)

 

Parallel Scavenge收集器能够配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成。只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用MaxGCPauseMillis参数(更关注最大停顿时间)或GCTimeRatio参数(更关注吞吐量)给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别

4、serial old(标记-整理)

深度解析(图文)JVM垃圾收集器(二)

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,优缺点与Searial相同

  • 在JDK1.5 以及之前版本(Parallel Old诞生以前)中与Parallel Scavenge收集器搭配使用。
  • 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用

5、parallel old(标记-整理)

深度解析(图文)JVM垃圾收集器(二)

parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法,这个收集器是在JDK 1.6中才开始提供的,在此之前,如果新生代选择了Parallel Scavenge收集器,老年代除了Serial Old以外别无选择,在多核CPU的环境中,这种组合吞吐量甚至不如ParNew加CMS。直到Parallel Old诞生以后,“吞吐量优先”收集器终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。 Parallel Old收集器的工作流程与Parallel Scavenge相同

6、cms(Concurrent Mark Sweep)(标记-清除)

深度解析(图文)JVM垃圾收集器(二)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合那些非常重视服务的响应速度的应用,例如互联网站或者B/S系统的服务端上的Java应用。从名字上(“Mark Sweep”)就可以看出它是基于“标记-清除”算法实现的。 CMS收集器工作的整个流程分为以下4个步骤

初始标记:仅标记一下GC Roots能直接关联到的对象;速度很快;但需要"Stop The World";

并发标记:进行GC Roots Tracing的过程;刚才产生的集合中标记出存活对象;应用程序也在运行;并不能保证可以标记出所有的存活对象;

重新标记:为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录;需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短,采用多线程并行执行来提升效率

并发清除:回收所有的垃圾对象

问题1:由于使用标记清除算法,所以会产生内存碎片,有人会觉得既然Mark Sweep会造成内存碎片,那么为什么不把算法换成Mark Compact呢?

答案:因为当并发清除的时候,用Compact整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact更适合“Stop the World”这种场景下使用

问题2:为什么Serial Old是CMS的备用老年代回收器呢?

答案:在老年代填满之前无法完成对象回收是指年老代在并发清除阶段清除不及时,因此造成的空闲内存不足。而不能满足内存的分配请求,则主要指的是新生代在提升到年老代时,由于年老代的内存碎片过多,导致一些分配由于没有连续的内存无法满足。
实际上,在并发模式失败的情况下,serial old会作为备选搜集器,进行一次全局GC(Full GC),因此serial old也算是CMS的“替补”。显然,由于serial old的介入,会造成较大的停顿时间。
为了尽量避免并发模式失败发生,我们可以调节-XX:CMSInitiatingOccupancyFraction=<N>参数,去控制当年老代的内存占用达到多少的时候(N%),便开启并发搜集器开始回收年老代。

所以由于有Con-current ModeFailure会触发FGC这个致命性的缺点,因此没有任何版本的JDK采用CMS作为默认的垃圾回收器

CMS收集器还提供了两个参数用于内存碎片

-XX:+UseCMS-CompactAtFullCollection开关参数(默认是开启的,此参数从JDK 9开始废弃),用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,(在Shenandoah和ZGC出现前)是无法并发的。这样空间碎片问题是解决了,但停顿时间又会变长,

-XX:CMSFullGCsBeforeCompaction(此参数从JDK 9开始废弃),这个参数的作用是要求CMS收集器在执行过若干次(数量由参数值决定)不整理空间的Full GC之后,下一次进入Full GC前会先进行碎片整理(默认值为0,表示每次进入Full GC时都进行碎片整理)

7、G1收集器(新老年代收集器)

在JDK 9中,G1被提议设置为默认垃圾收集器,G1是一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。

G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:

1、G1是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。

2、G1的Stop The World(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。

首先我们先了解G1中的这几个重要概念

Region

传统的GC收集器将连续的内存空间划分为新生代、老年代和永久代(JDK 8去除了永久代,引入了元空间Metaspace),这种划分的特点是各代的存储地址(逻辑地址,下同)是连续的。如下图所示:

深度解析(图文)JVM垃圾收集器(二)

而G1的各代存储地址是不连续的,每一代都使用了n个不连续的大小相同的Region,每个Region占有一块连续的虚拟内存地址。如下图所示:

深度解析(图文)JVM垃圾收集器(二)

我们注意到还有一些Region标明了H,它代表Humongous,这表示这些Region存储的是巨大对象(humongous object,H-obj),即大小大于等于region一半的对象。H-obj有如下几个特征:

1、 H-obj直接分配到了old gen,防止了反复拷贝移动。

2、H-obj在global concurrent marking阶段的cleanup 和 full GC阶段回收。

3、 在分配H-obj之前先检查是否超过 initiating heap occupancy percent和the marking threshold, 如果超过的话,就启动global concurrent marking,为的是提早回收,防止 evacuation failures 和 full GC。

为了减少连续H-objs分配对GC的影响,需要把大对象变为普通的对象,建议增大Region size。

一个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定

持续更新中

8、ZGC(后续更新)

 

 

 

三:Server/Client模式分别是什么意思

(1)32位window操作系统,不论硬件如何都默认使用Client的JVM模式
(2)32位其它操作系统,2G内存同时有2个CPU以上用Server模式,低于该配置还是Client模式
(3)64位都是 server模式

 

四:jdk版本默认的垃圾收集器

jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默认垃圾收集器G1

 

五:如何开启垃圾回收器以及搭配

 

使用 说明
-XX:+UseSerialGC 虚拟机运行再Client模式下的默认值,打开此开关后,使用Serial+Serial Old的收集器组合进行内存回收
-XX:+UseParNewGC 打开此开关后,使用ParNew+Serial Old的收集器组合进行内存回收
-XX:+UseConcMarkSweepGC 打开此开关后,使用ParNew+CMS+Serial Old的收集器组合进行内存
-XX:+UseParallelGC 虚拟机运行在Server模式下的默认值,打开此开关后,使用ParallelSeavenge+ParallelOld的收集器组合进行内存回收
-XX:+UseParallelOldGC 打开此开关后,使用ParallelSeavenge+ParallelOld的收集器组合进行内存回收
-XX:+UseG1GC 使用G1收集器进行内存回收


 

查看当前JVM正在使用的垃圾收集器

yyl@yyl-PC MINGW64 ~/Desktop
$ jps
15136 FilesApplication
6640 Jps
7392 RemoteMavenServer
6948
14620 Launcher

yyl@yyl-PC MINGW64 ~/Desktop
$ jinfo -flags 15136
Attaching to process ID 15136, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.171-b11
Non-default VM flags: 
-XX:-BytecodeVerificationLocal 
-XX:-BytecodeVerificationRemote 
-XX:CICompilerCount=3
 -XX:InitialHeapSize=134217728 
-XX:+ManagementServer 
-XX:MaxHeapSize=2122317824 
-XX:MaxNewSize=707395584
 -XX:MinHeapDeltaBytes=196608 
-XX:NewSize=44695552 
-XX:OldSize=89522176 
-XX:+PrintGCDetails
 -XX:TieredStopAtLevel=1 
-XX:+UseCompressedClassPointers
 -XX:+UseCompressedOops 
-XX:+UseFastUnorderedTimeStamps
 -XX:-UseLargePagesIndividualAllocation
 -XX:+UseSerialGC
Command line:  -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:54857,suspend=y,server=n -XX:+UseSerialGC -XX:+PrintGCDetails -XX:TieredStopAtLevel=1 -Xverify:none -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:C:\Users\yyl\.IntelliJIdea2019.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8

关于JVM中还有很多参数,后续再JVM调优中配合实战会慢慢讲解

 

 

修炼匠心,在重复的岁月里,对得起每一寸光阴深度解析(图文)JVM垃圾收集器(二)

上一篇:Java 8 - 并行流计算入门


下一篇:TBB