一张图看懂JVM之垃圾回收器详解

导读

感谢读者的反馈,在?图中更新了新生代Eden区以及两个Survivor区的默认空间占比的分配表示,这里按照10等份区分8/10、1/10、1/10分别表示8:1:1的关系,会更清晰点。新生代所采用的“标记-复制-清除”的算法进行垃圾回收,以及分代回收算法中老年代如何为这种空间分配比所提供担保策略,在文《一张图看懂JVM之垃圾回收算法详解》中有比较详细的叙述,大家可以看下,这里就不再赘述。

另外一个遗留问题是关于direct memorynative momery区别的问题,首先可以确认的是这两种称呼的内存肯定都是堆外内存,是不受JVM自动垃圾回收机制管理的。

从更为细致的角度区分,目前有一种解释是这样表述的:"direct memory,在Java的上下文中特指Java程序通过一组特定的API访问native memory,而这组API主要是通过DirectByteBuffer暴露出来的。而Native memory则是一个相对通用的概念,因为在Hotspot VM中,不受GC管理的内存都是native memory,direct memory只限定在特定的访问Native memory的做法,二者并不完全等价"。关于这个两个概念的解释,如果有更好的区分大家可以给我留言,这个问题实际上并不关键,只是有同学有疑问,这里就稍微解释下了。

回到本文的主题,在之前文章中我们对Java垃圾收集算法的特点有了一定的认识,但是对于JVM而言算法并不是实现,要进行GC操作JVM是通过特定的垃圾回收器来完成的,在?图中已经把目前比较常用的垃圾回收器,以及这些回收器分别适用于那些内存空间作了一个初步的展示,但是这些垃圾回收器的特点是什么?有什么样的优缺点?在本文中和大家探讨下。

垃圾回收器

垃圾收集器是垃圾收集算法的具体实现,由于Java虚拟机规范并没有对垃圾收集器如何实现进行明确的规定,所以不同的JVM厂商以及不同虚拟机版本所提供的垃圾收集器都可能会有较大的差别,并且一般也都会提供对应的参数,供用户根据自己的应用特点组合出各个内存区块,主要是新生代、老年代所使用的收集器。

本文以JDK1.7 Update14之后的Hotspot虚拟机,也是目前使用最为广泛的虚拟机为基础,和大家一起看看都有那些垃圾收集器,以及它们的特点是什么。

在详细讨论具体垃圾收集器之前,我们先看下这些收集器所适用的范围,以及它们的组合配对关系。

具体如下图所示:

上图展示了JDK1.7+后,Hotspot JVM的所有垃圾收集器以及它们适用的“代”,适合新生代的垃圾收集器有:Serial、ParNew、Parallel Scavenge、G1。适合年老代的垃圾收集器有:CMS、Serial Old、Parallel Old、G1。它们之间的组合关系如上图连线(粗线相连的是最佳组合),其中G1是JDK1.7 Update14这个版本中正式提供的商业收集器,它可以同时适用于新生代和年老代。

实践上在JDK1.7以后其实是可以统一采用G1收集器的(作者目前所在公司已经全部使用G1收集器),至于G1具体的特性,在本文稍后的内容中详细讲解。

- JAVA_OPTS=-Xms4g -Xmx4g -Xmn2g -XX:MaxDirectMemorySize=1g -XX:-OmitStackTraceInFastThrow -XX:+UseG1GC -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8 -XX:+DisableExplicitGC -verbose:gc -Xloggc:/opt/gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m

虽然实践上已然可以同一采用G1了,但是作为基础知识,我们还是对这些具体垃圾收集器有一个比较深刻的认识会比较好。下面就依次讨论下这些垃圾收集器的特点是什么吧!

Serial(新生代-串行-收集器)

Serial是一个比较古老的收集器,而且是一个单线程的收集器,这种收集器在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(采用的是复制算法)。所以试想下在互联网高并发的场景下采用这样的收集器显然是不可以的,所以我们目前在做后端服务时,不可能会用到这款收集器。

因为其简单、单线程的特点用在Java桌面程序场景会比较合适,但是大家也都知道目前采用Java开发桌面程序已经很少见了,所以大家对此了解就行。

其工作示意图如下:

ParNew(新生代-并行-收集器)

Parnew收集器是Serial的多线程版本,除了多线程收集外,其余部分与Serial相比并没有多大的差别,由于其可以与CMS收集器配合使用,所以在JDK1.7之前,对于Java服务应用来说是首选的新生代收集器。

示意图如下:

这种收集器与Serial一样,在进行垃圾回收时也会暂停所有用户工作线程,只是它采取了多线程回收,所以回收的速度会比Serial快,从而将卡顿时间缩短。

Parallel Scavenge (新生代-并行-收集器)

Parallel Scavenge也是一个新生代收集器,采用的也是多线程,以及复制收集算法,与其他同类型收集器不同的是,它的关注点是达到一个可控制吞吐量的目标,吞吐量=运行用户代码时间/(运行用户代码时间+垃圾回收的时间),假设虚拟机总共运行了100分钟,其中垃圾回收花了一分钟,那么吞吐量就是99%。高吞吐量的目的是为了高效的利用CPU时间,从而尽快的完成程序运算任务,主要适合后台运算不需要有太多交互的应用场景。

该收集器提供的控制参数有:

MaxGCPauseMillis:GC时间的最大值。

GCTimeRatio:GC时间占用总时间的比例。

UseAdaptiveSizePolicy:这个参数则是开启GC内存分配的自适应调整策略。可以自动调节,新生代的大小、Eden与Survivor的比例、晋升老年代对象的年龄。

Serial Old(年老代-串行-收集器)

Serial Old 收集器就是Serial的老年代版本,是一个单线程的,垃圾收集算法采用标记-整理算法的收集器。

Parallel Old(年老代-并行-收集器)

Parallel Old收集器是Parallel Scavenge的老年代版本,是多线程,采用标记-整理算法的收集器。

CMS(年老代-并行-收集器)

CMS(Concurrent Mark-Sweep)是一款以获取最短回收停顿时间为目标的收集器。比较适合互联网响应式应用场景,采用的是“标记-清除”算法。

其收集过程如下图所示:

如图所示,在初始标记和重新标记两个步骤,也会和Serial 一样暂停所有用户线程。

G1收集器

G1(Garbage First)是目前最为前沿的垃圾回收器,在前面的内容中已经提过JDK1.8以后的生产实践新生代、年老代都可以采用G1作为垃圾回收器。其采用的收集算法是”标记-清除-整理“,所以不会产生内存碎片。

关于G1的细节,如果想了解得更多,可以参考如下资料:

  • http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html
  • https://www.zhihu.com/question/50398881/answer/120831226

后记

在实践中关于GC收集器的选择,看目前来看,如果是互联网高并发场景的服务器端应用,可以统一使用G1就行。而如果是其他的应用场景,可能就需要根据合适的场景进行组合选择了。

另外GC收集器的技术发也不是一成不变的,在JDK11中已经出现了新的ZGC垃圾收集器,当然目前大家主流的JDK版本大部分还停留在JDK1.8,如果大家对此感兴趣,可以参考链接:

http://openjdk.java.net/projects/zgc/

好了,就给大家介绍到这里了,如果有什么问题可以关注我的公众号进行交流哦!

—————END—————

原文发布于微信公众号 - 无敌码农(jiangqiaodege)

原文发表时间:2018-10-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券