Darktea Home

关于 Java GC 已经有很多好的文档了, 比如这些:

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

http://blog.csdn.net/fenglibing/article/details/6321453

但是这里还是想再重点整理一下 Java GC 日志的格式, 可以作为实战时的备忘录.

同时也会再整理一下各种概念

一, JDK 6 提供的各种垃圾收集器

先整理一下各种垃圾收集器.

垃圾收集器:

垃圾收集器搭配总结:

组合起来有以下几种:

  1. Serial + Serial Old (UseSerialGC): GC 线程在做事情时, 其他所有的用户线程都必须停止 (即 stop the world)
  2. Serial + CMS: 一般不会这样配合使用
  3. ParNew + CMS (UseConcMarkSweepGC): 新生代的 GC 使用 ParNew, 有多个 GC 线程同时进行 Minor GC (主要是在多核的环境用多线程效果会好); 而老生代使用 CMS (CMS 后面会重点讲)
  4. ParNew + Serial Old (UseParNewGC): 新生代用 ParNew 的时候, 也可以选择老生代不用 CMS, 而用 Serial Old (实际上, 这个组合也不太常用)
  5. Parallel Scavenge + Serial Old (UseParallelGC): Parallel Scavenge 收集器的目的是达到一个可控制的吞吐率 (适用于各种计算任务); 这个组合中老生代仍旧使用 Serial Old
  6. Parallel Scavenge + Parallel Old (UseParallelOldGC): 新生代使用 Parallel Scavenge, 而 Parallel Old 是老年代版本的 Parallel Scavenge

这里要注意一下 UseParallelGC vs. UseParallelOldGC, 如果没有调好配置, UseParallelOldGC 有可能比 UseParallelGC 的性能还要差 (参考: http://softwareopolis.blogspot.com/2012/12/jvm-tuning-useparalleloldgc.html)

总结下来, 有 3 种场景:

二, Java 性能调优步骤

预先设定调优目标 -> 部署方案 (单 JVM 部署 vs. 多 JVM 部署) -> 32位 or 64位 -> 优化内存占用 -> 优化响应时间 -> 优化吞吐率

优化需要重点考量的 3 个因素:

调优过程中的 3 原则:

三, 内存占用调优

利用 Live Data Size 估算调优开始时的各项配置:

四, 响应时间调优

调优响应时间可能的方法:

调优步骤:

监控 Minor GC 响应时间频率 -> 调整新生代大小:

监视 Full GC 响应时间频率 -> 调整老生代大小:

五, CMS

CMS 的细节可以参考: http://blog.griddynamics.com/2011/06/understanding-gc-pauses-in-jvm-hotspots_02.html

这里只着重整理一下调优相关的东东.

1. PrintTenuringDistribution

先介绍一下对象 age 的概念. JVM 中的一个对象新被创建时 age 是 0; 之后每次 Minor GC 后, 这个对象如果还在新生代中, 这个对象的 age 数加一.

通过一个调优例子来说明 PrintTenuringDistribution 的用法

打开 JVM 的参数:

-XX:+PrintTenuringDistribution

这样 gc.log 会有 Tenuring 相关的信息. 例如:

Desired survivor size 8388608 bytes, new threshold 1 (max 15)
- age 1: 16690480 bytes, 16690480 total

简单点说, 从这个输出, 我们发现, 当前时刻 survivor 目标空间只有 8388608 bytes, 而小于实际占用的 16690480 bytes 的空间. 因此, 我们需要扩充 survivor 空间的大小.

需要把 survivor 空间扩大到多大呢?

需要 16690480 bytes 的空间来装入所有 age 为 1 的对象. 而 CMS 会用一个比率来估算需要分配多少 survivor 目标空间 (这个比率的默认值是 50%). 所以, survivor 空间需要扩充到: 16690480 / 50% = 33,380,960 bytes

结论: 把 survivor 空间的大小扩容到 33,380,960 bytes (大约 32M) 是合适的

这里再详细说明一下前面提到的 CMS 用来估算 survivor 目标空间占用的那个比率.

这个比率默认值是 50 (代表50%). 可以通过设置 JVM 的参数来配置, 例如:

-XX:TargetSurvivorRatio=90

不过一般来说, 极少情况需要配置这个值, 默认的 50 就 OK 了.

总之, 我们通过打开 PrintTenuringDistribution 获取更多的 GC 信息来优化对象从新生代到老生代的提升率, 以及优化 Minor GC 的响应时间.

2. 避免老生代的 stop-the-world

要避免老生代的 stop-the-world 就是要保证 CMS 回收的内存的速度比从新生代晋升到老生代的速度快. 这样才能保证老生代不被填满而造成 stop-the-world.

要保证 CMS 回收的速度, 要靠两个因素来保证:

对第一个因素, 我们尽量给老生代分配更多的空间就行了.

下面详细说说第二个因素.

GC 参数中有一个:

-XX:CMSInitiatingOccupancyFraction

默认值是68. 其含义是, 每次当老生代的总空间的 68% 被占用的时候, 就进行 CMS.

这个值调得越小, CMS 就会越早发生, 发生 stop-the-world 的概率就会更小;

这个值调得越大, CMS 就会越晚发生, 发生 stop-the-world 的概率就会更大.

所以我们可以通过调节这个值来调节 CMS 发生的时机, 避免 stop-the-world 的发生. 但是也不能调的太小了, 太小了的话, 会触发不必要的 CMS, 降低了吞吐率

另外需要注意, -XX:CMSInitiatingOccupancyFraction 要和 -XX:+UseCMSInitiatingOccupancyOnly 参数一起使用才会生效

六, 吞吐率

在响应时间调好以后, 可以考虑调吞吐率. 这里主要讲一下 CMS 吞吐率的调优方法:

注意: 以上 4 个调整方法需要和响应时间的调整做 trade off

七, 其他一些可以考虑调整的 JVM 参数:

另外, 需要特别注意 -XX:+ParallelRefProcEnabled 这个参数的使用, 在有些 JDK 版本, 设置了这个参数会导致程序 hang 住 (具体参考: http://www.codelve.com/archives/145 )

八, Reference