Java垃圾收集器(GC)简介与最佳组合探究

Java经过近20年的演变,已经发展出一套复杂、健壮和高性能的垃圾收集器。在不同的应用场合下使用不同的GC组合能让程序性能得到可观提高。我想这也是Java这么多年来一直处于不败之地的原因之一。

以下讨论只限于Server模式下的HotSpot JVM。

GC的类型

Sun/Oracle的HotSpot JVM为我们提供了多种不同的GC,一种GC只专门负责新生代或老年代的内存回收工作,所以实际使用的时候需要我们为新生代和老年代指定不同的GC。但G1例外,因为G1可以通吃整个堆内存。

Serial GC

Serial GC是最基本也是年纪最大的GC,它由Sun随第一版本的Java一同发布。 Serial是一个单线程的用于新生代的 GC,因此它在工作的只有一个线程来完成GC,同时还必须让JVM 暂停执行所有用户线程,即有名的Stop The World。 这就意味着程序会有较长时间的停顿。所以对于服务端应用来说,Serial GC基本无用武之地,JVM默认也不会在新生代选用此GC。

ParNew GC

ParNew GC基本上是Serial GC的多线程版本,即在新生代的GC过程中会有多个线程线程同时执行清理,其它与Serial无异。因为是多线程,所以在多CPU环境下,它的性能会比Serial强一些。但如果是单CPU环境,它会带来线程上下文切换的时间开销,性能反而会不如Serial。ParNew是Server模式下的JVM的默认新生代收集器

Parallel Scavenge GC

该收集器也是一个作用于新生代的并行的多线程收集器,它与ParNew最大的区别在于它更加关注吞吐量(吞吐量 = 运行用户代码的时间 / (运行用户代码时间 + GC执行的时间) )。为了达到此目的,该GC提供了-XX:MaxGCPauseMillis参数和-XX:GCTimeRatio来控制吞吐量。前者是一个大于0的毫秒值,GC会尽可能保证垃圾收集耗时不超过该值。后者是一个大于0小于100的整数,意为垃圾收集耗时占总运行时间的比例。一般情况下,如果你的程序不是特别需要对GC吞吐量进行优化的话也不会手动指定使用该GC。

Serial Old GC 和 Parallel Old GC

在名称后面加上old意为该GC是为老年代服务的。前者在老年代回收时与前面提到的Serial相同,即单线程,会造成较长的停顿。后者相当于多线程版本,工作时也会先暂所有用户线程,然后仅仅是会启动多个线程执行GC而已。

CMS GC

CMS(Concurrent Mark Sweep)是一种以尽可能减少回收停顿时间为目标的收集器,只能作用于老年代。前面的的GC在执行回收算法时,必须先将挂起所有用户线程,待GC完成后用户线程才得以继续执行。CMS与之最大的区别是,CMS的回收过程可以部分并发地与用户线程同时执行。 CMS的回收分为以下几个阶段:

  1. 初始标记
  2. 并发标记
  3. 重新标记
  4. 并发清除

其中,最耗时的步骤2和4是并发执行的,即用户线程不需要停顿,回收可以与用户线程同时执行,这样就能大大缩短Stop The World的时间。但这并不意味着CMS就是个非常完美的GC了,它还有以下几个主要缺点:

  • CMS会与用户线程抢夺CPU资源。因为CMS的主要过程会与用户线程并发执行,因此用户线程此时执行速度会有一定的下降。如果是在单CPU情况下,这种情况会更加严重。
  • CMS可能会出现Concurrent Mode Failure错误。因为CMS在执行并发标记时,用户线程也在执行,因此在标记的同时还会有新垃圾在不断产生,这部分垃圾称为浮动垃圾,这是CMS无法进行回收处理的,只能等待下一次GC再说。也正是由于GC时用户线程还会执行,CMS必须在开始GC之前为用户线程预留一部分内存空间以供使用,而这是有风险的。这部分空间不够用户线程使用, 就会导致Concurrent Mode Failure, JVM会被迫启用Serial Old触发一次Full GC, 而执行一次Full GC的耗时是比较长的。

PS:

- Minor GC: 指的是对新生代进行垃圾回收的过程,这个过程一般会非常迅速。

- Full GC: 指对老年代执行的GC过程,在执行Full GC之前也可能会先执行一次Minor GC。Full GC通常会比Minor GC慢很多。

G1收集器

G1是目前垃圾收集技术发展的最新成果之一,它与前面的几款GC最大的不同在于:

  • G1可管理整个堆区,包括新生代和老年代。
  • G1在物理上不区分新生代和老年代。G1会把整个堆划分为很多区域(Region),新生代和老年代现在变更了仅仅是逻辑上的概念,它们并不需要在物理上严格区分。
  • G1会对所有Region进行回收效率排序,优先清理回收效率最高的Region。

除此之外,G1与CMS也是并发执行的GC,即执行清理时可以与用户线程同时(并发)执行,但是G1可以做到比CMS更短暂的停顿时间。

GC的选择

  • 对于服务端应用,我个人认为应当优先使用G1。除了G1的很多优秀特性以外,还有一个很重要的原因是Oracle打算在未来能用G1取代前面所有的GC,也就是说Oracle在今后肯定会把对GC优化的主要精力放在G1上。但是Oracle还是偏向于保守,因为直到今天的Java8,如果你不指定GC的话,JVM依然是使用ParNew + Serial Old的GC组合。不过我们可以通过
-XX:UseG1GC

参数命令JVM使用G1。

  • 对于客户端应用,如果不加任何参数的话,JVM会选择Serial + Serial Old组合,很不给力。最好添加:
-XX:+UseConcMarkSweepGC

即命令JVM在老年代使用CMS,以提高GC性能。

时间: 2025-01-05 05:53:09

Java垃圾收集器(GC)简介与最佳组合探究的相关文章

【转】Java垃圾收集器

原文链接 http://www.cnblogs.com/gw811/archive/2012/10/19/2730258.html#top Java垃圾收集器 概述 说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史远远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.当Lisp还在胚胎时期时,人们就在思考: GC需要完成的三件事情: 哪些内存需要回收? 什么时候回收? 如

《深入理解java虚拟机》学习笔记四/垃圾收集器GC学习/一

Grabage Collection      GC GC要完成的三件事情: 哪些内存需要回收? 什么时候回收? 如何回收? 内存运行时区域的各个部分中: 程序计数器.虚拟机栈.本地方法栈这3个区域随线程而生,随线程而灭. 栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈的操作. 每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此, 这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需过多考虑回收的问题. 因为方法结束或者线程结束时,内存自然就跟着回收了. 而ja

HotSpot垃圾收集器GC的种类

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

垃圾收集器GC的种类

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

Java垃圾收集器标准详解及用途

概述 说起垃圾收集(Garbage Collection,GC),大部分人都把这项技术当做Java语言的伴生产物.事实上,GC的历史远远比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.当Lisp还在胚胎时期时,人们就在思考: GC需要完成的三件事情:  哪些内存需要回收? 什么时候回收? 如何回收? 经过半个世纪的发展,内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了"自动化"时代,那为什么我们还要去了解GC和内存分配呢?答

垃圾收集器GC

(1)DefNew(串行)收集器 Serial(串行)垃圾收集器是最基本.发展历史最悠久的收集器:JDK1.3.1前是HotSpot新生代收集的唯一选择: 特点: (1) 针对新生代采用复制算法,单线程收集器,进行垃圾收集时,必须暂停所有工作线程,直到工作完成.即会:Stop the World (2)应用场景: 依然是HotSpot在Client模式下默认的新生代收集器: 也有优于其他收集器的地方:            简单高效(与其他收集器的单线程相比):            对于限定单

二、Java如何分配和回收内存?Java垃圾收集器如何工作?

线程私有的内存区域随用户线程的结束而回收,内存分配编译期已确定,内存分配和回收具有确定性.共享线程随虚拟机的启动.结束而建立和销毁,在运行期进行动态分配.垃圾收集器主要对共享内存区域(堆和方法区)进行垃圾收集回收. Java如何实现内存动态分配和内存垃圾的回收? 1.哪些内存需要回收(垃圾收集器内存回收的对象)?已经"死亡"的对象,那如何判定对象已经"死亡"了? Java堆回收的内存:已经"死亡"的对象 方法区回收的内存:废弃的常量和无用的类 2

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

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

JAVA 垃圾收集器与内存分配策略

引言 垃圾收集技术并不是Java语言首创的,1960年诞生于MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言.垃圾收集技术需要考虑的三个问题是: 哪些内存需要回收 什么时候回收 如何回收 http://my.oschina.net/jiangmitiao/blog/470426 中讲到java内存运行时区域的分布,其中程序计数器,虚拟机栈,本地方法区都是随着线程而生,随线程而灭,所以这几个区域就不需要过多考虑回收问题.但是堆和方法区就不一样了,只有在程序运行期间我们才知道会创建哪