【深入理解JVM】:HotSpot垃圾收集器

相关概念

并发和并行

这两个名词都是并发编程中的概念,在谈论垃圾收集器的上下文语境中,它们可以解释如下。

  • 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

Minor GC 和 Full GC

  • 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
  • 老年代GC(Major GC / Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。

吞吐量

吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即

吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。

虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

HotSpot垃圾收集器

在虚拟机规范中并没有对垃圾回收器如何实现具体介绍,因此每个厂商的垃圾回收器可能会完全不同,但是我们介绍的是基于JDK1.7之后的Hotspot虚拟机(包括前面对Java虚拟机的介绍也是基于jdk1.7版本的)。在Hotspot中,虚拟机的收集器主要有下:

可以看到垃圾收集器是按对象的分代来划分的,可以用双箭头连接的垃圾收集器表示两者可以配合使用。可以看到新生代垃圾收集器有Serial、ParNew、Parallel Scavenge,G1,属于老年代的垃圾收集器有CMS、Serial Old、Parallel Old和G1.其中的G1是一种既可以对新生代对象也可以对老年代对象进行回收的垃圾收集器。然而,在所有的垃圾收集器中,并没有一种普遍使用的垃圾收集器。在不同的场景下,每种垃圾收集器有各自的优势。

Serial收集器

Serial收集器是最基本、发展历史最悠久的收集器。它是一种单线程垃圾收集器,这就意味着在其进行垃圾收集的时候需要暂停其他的线程,也就是之前提到的”Stop the world“。虽然这个过程是在用户不可见的情况下把用户正常的线程全部停掉,听起来有点狠,这点是很难让人接受的。Serial、Serial Old收集器的工作示意图如下:

尽管由以上不能让人接受的地方,但是Serial收集器还是有其优点的:简单而高效,对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得较高的手机效率。到目前为止,Serial收集器依然是Client模式下的默认的新生代垃圾收集器。

ParNew收集器

可ParNew收集器是Serial收集器的多线程版本,ParNew收集器的工作示意图如下:

ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器。除去性能因素,很重要的原因是除了Serial收集器外,目前只有它能与CMS收集器配合工作。

但是,在单CPU环境中,ParNew收集器绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越Serial收集器。然而,随着可以使用的CPU的数量的增加,它对于GC时系统资源的有效利用还是很有好处的。

Parallel Scavenge收集器

Parallel Scavenge收集器是新生代垃圾收集器,使用复制算法,也是并行的多线程收集器。与ParNew收集器相比,很多相似之处,但是Parallel Scavenge收集器更关注可控制的吞吐量。吞吐量越大,垃圾收集的时间越短,则用户代码则可以充分利用CPU资源,尽快完成程序的运算任务。

Parallel Scavenge收集器使用两个参数控制吞吐量:

  • XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间
  • XX:GCRatio 直接设置吞吐量的大小。

直观上,只要最大的垃圾收集停顿时间越小,吞吐量是越高的,但是GC停顿时间的缩短是以牺牲吞吐量和新生代空间作为代价的。比如原来10秒收集一次,每次停顿100毫秒,现在变成5秒收集一次,每次停顿70毫秒。停顿时间下降的同时,吞吐量也下降了。

除此之外,Parallel Scavenge收集器还可以设置参数-XX:+UseAdaptiveSizePocily来动态调整停顿时间或者最大的吞吐量,这种方式称为GC自适应调节策略,这点是ParNew收集器所没有的。

Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,采用“标记-整理算法”进行回收。其运行过程与Serial收集器一样。

Serial Old收集器的主要意义也是在于给Client模式下的虚拟机使用。如果在Server模式下,那么它主要还有两大用途:一种用途是在JDK 1.5以及之前的版本中与Parallel Scavenge收集器搭配使用,另一种用途就是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法进行垃圾回收。其通常与Parallel Scavenge收集器配合使用,“吞吐量优先”收集器是这个组合的特点,在注重吞吐量和CPU资源敏感的场合,都可以使用这个组合。

CMS收集器

CMS收集器(Concurrent Mark Sweep)的目标就是获取最短回收停顿时间。在注重服务器的响应速度,希望停顿时间最短,则CMS收集器是比较好的选择。

整个执行过程分为以下4个步骤:

  • 初始标记
  • 并发标记
  • 重新标记
  • 并发清除

初始标记和重新标记这两个步骤仍然需要暂停Java执行线程,初始标记只是标记GC Roots能够关联到的对象,并发标记就是执行GC Roots Tracing的过程,而重新标记就是为了修正并发标记期间因用户程序执行而导致标记发生变动使得标记错误的记录。其执行过程如下:

由上图可知,整个过程中好使最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,因此,总体上CMS收集器的内存回收过程食欲用户线程一起并发执行的。

CMS的优点很明显:并发收集、低停顿。由于进行垃圾收集的时间主要耗在并发标记与并发清除这两个过程,虽然初始标记和重新标记仍然需要暂停用户线程,但是从总体上看,这部分占用的时间相比其他两个步骤很小,所以可以认为是低停顿的。

尽管如此,CMS收集器的缺点也是很明显的:

  • 对CPU资源太敏感,这点可以这么理解,虽然在并发标记阶段用户线程没有暂停,但是由于收集器占用了一部分CPU资源,导致程序的响应速度变慢
  • CMS收集器无法处理浮动垃圾。所谓的“浮动垃圾”,就是在并发标记阶段,由于用户程序在运行,那么自然就会有新的垃圾产生,这部分垃圾被标记过后,CMS无法在当次集中处理它们(为什么?原因在于CMS是以获取最短停顿时间为目标的,自然不可能在一次垃圾处理过程中花费太多时间),只好在下一次GC的时候处理。这部分未处理的垃圾就称为“浮动垃圾”
  • 由于CMS收集器是基于“标记-清除”算法的,前面说过这个算法会导致大量的空间碎片的产生,一旦空间碎片过多,大对象就没办法给其分配内存,那么即使内存还有剩余空间容纳这个大对象,但是却没有连续的足够大的空间放下这个对象,所以虚拟机就会触发一次Full GC(这个后面还会提到)这个问题的解决是通过控制参数-XX:+UseCMSCompactAtFullCollection,用于在CMS垃圾收集器顶不住要进行FullGC的时候开启空间碎片的合并整理过程。

G1收集器

G1(Garbage-First)收集器是现今收集器技术的最新成果之一,之前一直处于实验阶段,直到jdk7u4之后,才正式作为商用的收集器。

与前几个收集器相比,G1收集器有以下特点:

  • 并行与并发
  • 分代收集(仍然保留了分代的概念)
  • 空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
  • 可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)

此外,G1收集器将Java堆划分为多个大小相等的Region(独立区域),新生代与老年代都是一部分Region的集合,G1的收集范围则是这一个个Region(化整为零)。

G1的工作过程如下:

  • 初始标记(Initial Marking)
  • 并发标记(Concurrent Marking)
  • 最终标记(Final Marking)
  • 筛选回收(Live Data Counting and Evacuation)

初始标记阶段仅仅只是标记一下GC Roots能够直接关联的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段的用户程序并发运行的时候,能在正确可用的Region中创建对象,这个阶段需要暂停线程。并发标记阶段从GC Roots进行可达性分析,找出存活的对象,这个阶段食欲用户线程并发执行的。最终标记阶段则是修正在并发标记阶段因为用户程序的并发执行而导致标记产生变动的那一部分记录,这部分记录被保存在Remembered Set Logs中,最终标记阶段再把Logs中的记录合并到Remembered Set中,这个阶段是并行执行的,仍然需要暂停用户线程。最后在筛选阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划。整个执行过程如下:

垃圾收集器常用参数总结

client/serrver端不同的GC方式:

Sun JDK HotSpot虚拟机GC组合方式:

参考

1、周志明,深入理解Java虚拟机:JVM高级特性与最佳实践,机械工业出版社

时间: 2024-10-06 23:38:00

【深入理解JVM】:HotSpot垃圾收集器的相关文章

深入理解JVM:垃圾收集器与内存分配策略

堆里面存放着Java世界几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还存活,哪些已经死去.判断对象的生命周期是否结束有以下几种方法 引用计数法 具体操作是给对象添加一个引用计数器,每当有一个地方引用时,计数器的值就加1,:当引用失效时,计数器就减1:任何时刻计数器为0的对象就 是不可能再被使用的.客观的说引用计数器算法实现简单,判定效率也很高,在大部分情况下他都是一个不错的算法.但是引用计数器有缺陷 举个简单的例子,对象A和对象B都有字段instance,

深入理解JVM(5)——HotSpot垃圾收集器详解

HotSpot虚拟机提供了多种垃圾收集器,每种收集器都有各自的特点,没有最好的垃圾收集器,只有最适合的垃圾收集器.根据新生代和老年代各自的特点,我们应该分别为它们选择不同的收集器,以提升垃圾回收效率. 新生代垃圾收集器: Serial垃圾收集器 a)        单线程:只开启一条GC线程进行垃圾回收,并且在垃圾回收过程中停止一切用户线程,从而用户的请求或图形化界面会出现卡顿. b)        适合客户端应用 c)        简单高效:由于Serial收集器只有一条GC线程,因此避免了

JVM之垃圾收集器 (GC) 与内存分配策略

1.为什么要学习GC? GC (Garbage Collection)早于java出现,60年代出现的Lisp中最早使用了GC. 当需要排查各种内存溢出.内存漏斗问题时,当垃圾回收成为系统达到更高并发量的瓶颈时,就需要用到gc了. 总之,写出高性能的Java程序需要懂GC. 2.GC在JVM的体系结构中的位置 HotSpot JVM体系结构. 和应用性能相关的部分用紫色标出,调优从它们着手! 3.什么是性能? 在对Java应用程序进行调优时,主要关注两点:响应速度和吞吐量. 3.1响应速度 响应

深入理解java虚拟机----->垃圾收集器与内存分配策略(下)

1.  前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保  2.  垃圾收集器与内存分配策略 Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决两个问题: 给对象分配内存; 回收分配给对象的内存. 对象的内存分配,往大方向上讲就是在堆上的分配,对象主要分配在新生代的Eden区上.少数也可能分配在老年代,取决于哪一种垃圾收集器组合,还有虚拟机中的相关内存的参

6.HotSpot垃圾收集器

HotSpot JVM收集器 上面有7中收集器,分为两块,上面为新生代收集器,下面是老年代收集器.如果两个收集器之间存在连线,就说明它们可以搭配使用. 并发和并行 先解释下什么是垃圾收集器的上下文语境中的并行和并发: 并行(Parallel):指多条垃圾收集器线程并行工作,但此时用户线程仍然处于等待. 并发(Concurrent):指用户线程与垃圾收集器线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集器程序运行于另一个CPU之上. Serial(串行GC)收集器

深入理解JAVA虚拟机 垃圾收集器和内存分配策略

引用计数算法 很多教科书判断对象是否存活的算法是这样的:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1:当引用失效时,计数器值就减1:任何时刻计数器都为0的对象就是不可能再被使用的. 客观地说,引用计数算法(Reference Counting)的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法,也有一些比较著名的应用案例,例如微软的COM(Component Object Model)技术.使用ActionScript 3的FlashPlayer.Python语

深入理解 Java G1 垃圾收集器--转

原文地址:http://blog.jobbole.com/109170/?utm_source=hao.jobbole.com&utm_medium=relatedArticle 本文首先简单介绍了垃圾收集的常见方式,然后再分析了G1收集器的收集原理,相比其他垃圾收集器的优势,最后给出了一些调优实践. 一,什么是垃圾回收 首先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么?简单的说垃圾回收就是回收内存中不再使用的对象. 垃圾回收的基本步骤 回收的步骤有2步: 查找内存中不再使用的对象 释放

JVM学习十:JVM之垃圾收集器及GC参数

接近两个月左右没有写博客,主要是因为小孩过来后,回家比较忙,现在小孩端午送回家了,开始继续之前的JVM学习之路,前面学习了GC的算法和种类,那么本章则是基于算法来产生实际的用途,即垃圾收集器. 一.堆的回顾 新生代中的98%对象都是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块比较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor.当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor

HotSpot垃圾收集器GC的种类

堆内存的结构: 垃圾收集器就是垃圾收集算法的具体实现了.不同虚拟机所提供的垃圾收集器可能会有很大差别,我们使用的是HotSpot,HotSpot这个虚拟机所包含的所有收集器如图: 上图展示 了7种作用于不同分代的收集器,如果两个收集器之间存在连线,那说明它们可以搭配使用.虚拟机所处的区域说明它是属于新生代收集器还是老年代收集器.多说 一句,我们必须姚明带一个道理:没有最好的垃圾收集器,更加没有万能的收集器,只能选择对具体应用最合适的收集器.这也是HotSpot为什么要实现这么 多收集器的原因.O