如果系统提供或者消费 服务, 则JVM会定期执行 full GC 来确保本地未使用的对象在另一端也不占用空间. 记住, 即使你的代码中没有发布 RMI 服务, 但第三方或者工具库也可能会打开 RMI 终端. 最常见的元凶是 JMX, 如果通过JMX连接到远端, 底层则会使用 RMI 发布数据。
问题是有很多不必要的周期性 full GC。查看老年代的使用情况, 一般是没有内存压力, 其中还存在大量的空闲区域, 但 full GC 就是被触发了, 也就会暂停所有的应用线程。
这种周期性调用 删除远程引用的行为, 是在 sun.rmi.transport.ObjectTable
类中, 通过 sun.misc.GC.requestLatency(long gcInterval)
调用的。
对许多应用来说, 根本没必要, 甚至对性能有害。 禁止这种周期性的 GC 行为, 可以使用以下 JVM 参数:
这让 Long.MAX_VALUE
毫秒之后, 才调用 System.gc()
), 实际运行的系统可能永远都不会触发。
可以看到, 默认值为 3600000L
,也就是1小时触发一次 Full GC。
如果在程序启动时指定了 Java Agent (), agent 就可以使用 标记堆中的对象。agent 使用tagging的种种原因本手册不详细讲解, 但如果 tagging 标记了大量的对象, 很可能会引起 GC 性能问题, 导致延迟增加, 以及吞吐量降低。
问题发生在 native 代码中, JvmtiTagMap::do_weak_oops
在每次GC时, 都会遍历所有标签(tag),并执行一些比较耗时的操作。更坑的是, 这种操作是串行执行的。
如果存在大量的标签, 就意味着 GC 时有很大一部分工作是单线程执行的, GC暂停时间可能会增加一个数量级。
检查是否因为 agent 增加了GC暂停时间, 可以使用诊断参数 –XX:+TraceJVMTIObjectTagging
. 启用跟踪之后, 可以估算出内存中 tag 映射了多少 native 内存, 以及遍历所消耗的时间。
如果你不是 agent 的作者, 那一般是搞不定这类问题的。除了提BUG之外你什么都做不了. 如果发生了这种情况, 请建议厂商清理不必要的标签。
如果使用 G1 垃圾收集算法, 会产生一种巨无霸对象引起的 GC 性能问题。
说明: 在G1中, 巨无霸对象是指所占空间超过一个小堆区(region)
50%
的对象。
- 如果某个 region 中含有巨无霸对象, 则巨无霸对象后面的空间将不会被分配。如果所有巨无霸对象都超过某个比例, 则未使用的空间就会引发内存碎片问题。
要监控是否存在巨无霸对象, 可以打开GC日志, 使用的命令如下:
GC 日志中可能会发现这样的部分:
这样的日志就是证据, 表明程序中确实创建了巨无霸对象. 可以看到: G1 Humongous Allocation
是 GC暂停的原因。 再看前面一点的 allocation request: 1048592 bytes
, 可以发现程序试图分配一个 1,048,592
字节的对象, 这要比巨无霸区域(2MB
)的 多出 16 个字节。
第一种解决方式, 是修改 region size , 以使得大多数的对象不超过 50%
, 也就不进行巨无霸对象区域的分配。 region 的默认大小在启动时根据堆内存的大小算出。但也可以指定参数来覆盖默认设置, -XX:G1HeapRegionSize=XX
。 指定的 region size 必须在 1~32MB
之间, 还必须是2的幂 【2^10 = 1024 = 1KB; 2^20=1MB; 所以 region size 只能是: 1m
,2m
,4m
,8m
,,32m
】。
这种方式也有副作用, 增加 region 的大小也就变相地减少了 region 的数量, 所以需要谨慎使用, 最好进行一些测试, 看看是否改善了吞吐量和延迟。
更好的方式需要一些工作量, 如果可以的话, 在程序中限制对象的大小。最好是使用分析器, 展示出巨无霸对象的信息, 以及分配时所在的堆栈跟踪信息。
JVM上运行的程序多种多样, 启动参数也有上百个, 其中有很多会影响到 GC, 所以调优GC性能的方法也有很多种。
原文链接: GC Tuning: In Practice