Java垃圾回收精粹 — Part4

Java垃圾回收精粹分4个部分,本篇是第4部分。在第4部分里介绍了G1收集器、其他并发收集器以及垃圾收集监控和调优。

Garbage First (G1) 收集器

G1 (-XX:+UseG1GC)收集器是一个新的收集器。G1随Java 6发布,在Java 7U4中得到正式支持。它是一个部分并发的收集算法,通过尝试小量增加全局暂停的方式压缩年老区,将FullGC降到最低。因为碎片引起的FullGC正是CMS的一大麻烦。G1也是分代收集器,但是它与其他收集器使用不同的堆组织方式。根据不同的用途,G1将堆划分为一大批(约2000个)固定大小的区,而不是相同用途的堆连续在一起。

G1并发地标记区域以跟踪区域之间的引用,同时关注收集区域中的最大空闲空间。这些区域在递增的全局暂停中被收集,存活的对象被剪切到一个空的区域里面,这样整个过程就是压缩的。在同一个周期里收集的区域叫做Collection Set。

译注:G1会跟踪各个区域中的垃圾堆积的价值大小、回收后获得的空间大小以及回收所需时间,在后台维护一个优先列表。每次根据允许的收集时间,优先回收价值最大的区域,也就是Garbage-First名称的来由。

超过区域大小50%的对象会在大区域里分配,其大小可以达到当前区域大小的数倍大。G1收集和分配大对象操作的开销非常大,更加悲剧的是,目前还没有任何优化措施。

任何压缩收集器所遇到的挑战不是去移动对象,而是更新这些对象的引用。如果一个对象被许多个区域引用,那么更新这些引用肯定会比移动对象更加耗时。G1通过“记录集(Remembered Sets)” 跟踪区域中被其他区域引用的那些对象。记忆集是一些卡片的集合,这些卡片上标记着更新信息。如果记忆集变大,那么G1就会显著变慢。当从一个区域转移对象到另一区域时,由此引发的全局暂停时间与需要扫描和更新引用区域的数量成正比。

维护记录集会增加次要回收的成本,结果导致花费的时间比并行旧生代收集器和CMS收集器中次要回收暂停的时间更久。

G1是目标驱动的,可以通过“–XX:MaxGCPauseMillis=<n>”设置延迟时间,默认是200ms。该参数只会尽可能影响每个周期的工作量,但是不保证最终效果。设置为几十毫秒大多是徒劳的,而且几十毫秒也不是G1关注的目标。

如果你的应用程序可以容忍暂停0.5-1.0秒的增量压缩时间,而且此应用拥有一个会逐渐碎片化的堆,那么G1会是通用的收集器的一个很好选择。最坏情况是碎片引起的暂停,我们之前在CMS那儿也见过。G1 倾向于减少这种暂停的频率,因为那会花费额外的次要回收和对年老代的增量压缩。大部分的暂停被限制在区域层面而不是整个堆的压缩。

与CMS一样,G1也会因为无法保证晋升率而失败,最终求助于全局暂停的FullGC。就像CMS存在“并发模式失败”一样,G1也可能遭遇转移失败,在日志中看到“到达空间溢出(to-space overflow)”。没有空余区域可供对象转移进去,跟晋升失败类似。如果发生这种情况,试试使用更大的堆、更多的标记线程,但在某些情况下,需要应用程序作出改变以减少分配比率。

G1的一个有挑战性的问题是,如何处理好高关注率的对象和区域。 当区域里的存活对象没有被其他区域大量引用时,增量的全局暂停进行压缩会很高效。如果一个对象或者区域被大量引用了,记录集将会相应地变大,并且G1会避免收集这些对象。最终没有办法,只能频繁地使用中等长度的暂停时间来压缩堆大小。

其他并发收集器

CMS和G1通常被称为并发性最好的收集器。然而当你观察整个工作过程,很显然地,新生代、晋升、甚至多数年老代的工作根本不是并发的。CMS对年老代来说是并发性最好的算法,G1更像是全局暂停的增量收集器。CMS和G1都明显地拥有规律的全局暂停发生,并且在最坏情况下他们不适合严格的低延迟应用,比如金融交易或用户交互界面。

其他可用的的收集器还有:Oracle JRockit Real Time、IBM WebSphere Real Time、 Azul Zing。JRockit和Websphere收集器大多数情况下在延迟上控制得比CMS和G1好,但是在大多数情况下它们有吞吐量的限制,并且会有明显的全局暂停。Zing是洒家知道的一款能对所有代都真正并发收集和压缩,同时保持了高吞吐率的Java收集器。Zing确实有一些亚毫秒级的全局暂停,但这些是在与存活对象集大小无关的收集周期里完成。

JRockit Real Time在堆大小合适时,可以将暂停时间控制在几十毫秒,但是偶尔也会失败转为完全压缩暂停。WebSphere RealTime通过约束分配率和存活集的大小,可以将暂停时间控制在几毫秒。Zing在所有阶段并发以保证高分配率,从而实现亚毫秒级的暂停,包括次要收集阶段。无论堆大小,Zing都能够保持行为的一致性。如果需要保证程序的吞吐量或者需要控制对象模型状态,用户完全可以添加更大的堆而不用担心增加暂停时间。

对于所有的并发收集器来说,如果你关注延迟,就必须牺牲吞吐量换取空间。依据并发收集器的效率,你可能放弃一点点的吞吐量,但是总能显著地增加空间。如果真正实现了并发很少会发生全局暂停,但是这就需要更多CPU内核来支持并发操作和维持吞吐量。

注意:当分配的空间足够时,所有的并发收集器倾向于更高效地运行。第一条经验,你应该预算至少两到三倍存活集大小以确保高效地操作。然而,大量并发操作所需的空间随着应用程序的吞吐量以及与之相关的对象分配和晋升率增长而增加。因此,对于高吞吐量的应用,维持较高的存活集的堆大小比率很有必要。鉴于今天的服务器拥有巨大的内存空间,这些不成问题。

垃圾收集监控和调优

要理解你的应用程序和垃圾收集器是如何工作的,启动JVM时要至少添加以下参数:


1

2

3

4

5

6

7

-verbose:gc

-Xloggc:

-XX:+PrintGCDetails

-XX:+PrintGCDateStamps

-XX:+PrintTenuringDistribution

-XX:+PrintGCApplicationConcurrentTime

-XX:+PrintGCApplicationStoppedTime

然后加载日志到像Chewiebug这样的工具进行分析。

为了看到GC动态运行,请启动JVisualVM并且安装Visual GC插件。接下来你就能看见你的应用程序的GC,行为如下图:


要理解应用堆GC的要求,你需要一个有代表性且可以重复执行的负载测试。随着你堆每个收集器如何工作的理解,通过不同的配置运行负载测试,直至达到你期望的吞吐量和延迟。从最终用户的视角来看,测量延迟是最重要的。可以通过捕获每个测试请求的响应时间,将之记在直方图上,比如HdrHistogram或者Disruptor Histogram,这样你就能分析出更多东西。如果延迟峰值超出可接受范围,可以尝试关联GC日志来判断是否是GC出了问题。还有可能是其他问题导致的延迟高峰。另一个值得考虑的工具是jHiccup,可以用它来跟踪JVM暂停并且可以和系统合为一个整体。使用jHiccup测量空闲系统几个小时,通常会让你得到一个令人惊讶的结果。

如果延迟峰值由GC导致,那么你可以看看CMS或G1看是否可满足这个延迟目标。有时这是不可能的,因为高分配率和晋升率与低时延的要求是冲突的。GC调优是一个高技巧性的锻炼,常常需要修改程序以减少对象分配率或对象生命周期。如果需要权衡时间、GC资源优化和应用程序的修改及精通,那么可能必需购买商业的并发压缩JVM,比如JRockit Real Time 和 Azul Zing。

原文链接: mechanical-sympathy 翻译: ImportNew.com邢 敏
译文链接: http://www.importnew.com/8352.html

时间: 2024-10-07 19:40:09

Java垃圾回收精粹 — Part4的相关文章

Java垃圾回收精粹 — Part1

Java垃圾回收精粹分4个部分,本篇是第1部分.在第1部分里介绍了权衡点.对象生命周期以及全局暂停事件. 串行.并行.并发.CMS.G1.年轻代(Young Gen).新生代(New Gen).旧生代(Old Gen).持久代(Perm Gen).伊甸区(Eden).年老区(Tenured).幸存区(Survivor Spaces).安全点(Safepoints)以及数百种JVM启动参数.当你试图调整垃圾回收器使你的Java应用程序获得需要的吞吐量和延迟时,这些会难倒你吗?如果回答是,你也不必担

Java垃圾回收精粹 — Part3

Java垃圾回收精粹分4个部分,本篇是第3部分.在第3部分里介绍了串行收集器.并行收集器以及并发标记清理收集器(CMS). 串行收集器(Serial Collector) 串行收集器是最简单的收集器,对于单处理器系统真是绝佳上选.当然,它也是所有收集器里面最不常用的.串行收集器使用一个单独的线程进行收集,不管是次要收集还是主要收集.在年老区中分配的对象使用一个简单的凹凸指针算法(bump-the-pointer algorithm)即可.当tenured space填满后会触发主要回收. 译注:

Java 垃圾回收机制(早期版本)

Java 垃圾回收机制在我们普通理解来看,应该视为一种低优先级的后台进程来实现的,其实早期版本的Java虚拟机并非以这种方式实现的. 先从一种很简单的垃圾回收方式开始. 引用计数 引用计数是一种简单但是速度很慢的垃圾回收技术. 每个对象都含有要给引用计数器,当有引用连接至对象时,引用计数+1. 当引用离开作用域或者被置为null时,引用计数-1. 当发现某个对象的引用计数为0时,就释放其占用的空间.   这种方法开销在整个程序生命周期中持续发生,并且该方法有个缺陷,如果对象之间存在循环引用,可能

Java垃圾回收机制的工作原理

Java垃圾回收机制的工作原理 [博主]高瑞林 [博客地址]http://www.cnblogs.com/grl214 一.Java中引入垃圾回收机制的作用 当我们建完类之后,创建对象的同时,进行内存空间的分配,为了防止内存空间爆满,java引入了垃圾回收机制,将不再引用的对象进行回收,释放内存,循环渐进,从而防止内存空间不被爆满. 1.垃圾回收机制的工作原理 创建的对象存储在堆里面,把堆比喻为院子中的土地,把对象比喻为土地的管理者,院子比喻为java虚拟机,当创建一个对象时,java虚拟机将给

Java GC专家系列2:Java 垃圾回收的监控

这是”成为GC专家系列”文章的第二篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.到目前为止,你应该已经了解了JDK 7中的5种GC类型,以及每种GC对性能的影响. 在本篇中,我将介绍JVM在真实环境中如何运行GC的. 什么是GC监控 GC监控 指的是在运行时跟踪JVM运行GC的过程.例如,通过GC监控,我们能找出: 何时新生代的对象会被移动到老年代,有多少对象被移到了老年代. 何时stop-the-world发生以及持续时间. 通

Java深度历险(四)——Java垃圾回收机制与引用类型

Java语言的一个重要特性是引入了自动的内存管理机制,使得开发人员不用自己来管理应用中的内存.C/C++开发人员需要通过malloc/free 和new/delete等函数来显式的分配和释放内存.这对开发人员提出了比较高的要求,容易造成内存访问错误和内存泄露等问题.一个常见的问题是会产生“悬挂引用(dangling references)”,即一个对象引用所指向的内存区块已经被错误的回收并重新分配给新的对象了,程序如果继续使用这个引用的话会造成不可预期的结果.开发人员有可能忘记显式的调用释放内存

java 垃圾回收机制

1. java的垃圾回收机制主要工作包括:确定哪些对象属于垃圾,回收无用的对象占用的空间,使堆中的存活对象紧密排列. 2. Java垃圾回收的算法: 引用计数(基本不用): 当引用指向一个对象时,该对象的引用计数器+1: 当引用离开对象挥着被标记为null时,引用计数器-1: 当引用计数器为0时,释放对象占用的空间. 缺陷:如果对象间存在循环引用,可能会发生对象无法被回收的情况. 停止-复制: 对象发现: 从堆栈和静态存储区出发遍历所有引用,找到引用的对象以及该对象包含的所有引用,从而找到所有活

Java垃圾回收介绍(译)

在Java中,对象内存空间的分配与回收是由JVM中的垃圾回收进程自动完成的.与C语言不同的是,在Java中开发者不需要专门为垃圾回收写代码.这是使Java流行的众多特征之一,也帮助了程序员写出了更好的Java应用. 这是一个四部分的系列教程,以了解在Java中垃圾回收的基础, Java垃圾回收介绍 Java垃圾回收如何工作? Java垃圾回收器的类型 监控和分析Java垃圾回收 这篇教程是系列中的第一部分.本篇中会解释一些基本术语,像JDK,JVM,JRE,HotSpot VM,然后去理解JVM

Java垃圾回收机制以及内存泄漏

原文地址 前言 在segmentfault上看到一个问题:java有完善的GC机制,那么在java中是否会出现内存泄漏的问题,以及能否给出一个内存泄漏的案例.本问题视图给出此问题的完整答案. 垃圾回收机制简介 在程序运行过程中,每创建一个对象都会被分配一定的内存用以存储对象数据.如果只是不停的分配内存,那么程序迟早面临内存不足的问题.所以在任何语言中,都会有一个内存回收机制来释放过期对象的内存,以保证内存能够被重复利用. 内存回收机制按照实现角色的不同可以分为两种,一种是程序员手动实现内存的释放