使用UseConcMarkSweepGC减少JVM暂停时间> 1秒

san*_*ity 10 java performance garbage-collection jvm jvm-arguments

我在一台具有16Gb RAM,8核处理器和Java 1.6的机器上运行内存密集型应用程序,所有这些都运行在CentOS 5.2版(最终版)上.精确的JVM详细信息是:

java version "1.6.0_10"
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) 64-Bit Server VM (build 11.0-b15, mixed mode)
Run Code Online (Sandbox Code Playgroud)

我正在使用以下命令行选项启动应用程序:

java -XX:+UseConcMarkSweepGC -verbose:gc -server -Xmx10g -Xms10g ...
Run Code Online (Sandbox Code Playgroud)

我的应用程序公开了一个JSON-RPC API,我的目标是在25ms内响应请求.不幸的是,我看到延迟超过1秒,似乎是垃圾收集造成的.以下是一些较长的例子:

[GC 4592788K->4462162K(10468736K), 1.3606660 secs]
[GC 5881547K->5768559K(10468736K), 1.2559860 secs]
[GC 6045823K->5914115K(10468736K), 1.3250050 secs]
Run Code Online (Sandbox Code Playgroud)

这些垃圾收集事件中的每一个都伴随着延迟的API响应,其持续时间与显示的垃圾收集的长度非常相似(在几毫秒内).

以下是一些典型示例(这些示例均在几秒钟内生成):

[GC 3373764K->3336654K(10468736K), 0.6677560 secs]
[GC 3472974K->3427592K(10468736K), 0.5059650 secs]
[GC 3563912K->3517273K(10468736K), 0.6844440 secs]
[GC 3622292K->3589011K(10468736K), 0.4528480 secs]
Run Code Online (Sandbox Code Playgroud)

问题是我认为UseConcMarkSweepGC可以避免这种情况,或者至少使它非常罕见.相反,超过100毫秒的延迟几乎每分钟发生一次或更多(尽管超过1秒的延迟相当罕见,可能每10或15分钟一次).

另一件事是我认为只有一个FULL GC会导致线程被暂停,但这些似乎不是完整的GC.

可能需要注意的是,大多数内存都是由使用软引用的LRU内存缓存占用的.

任何帮助或建议将不胜感激.

Edd*_*die 11

首先,查看Java SE 6 HotSpot [tm]虚拟机垃圾收集调整文档,如果您还没有这样做的话.该文档说:

并发收集器在应用程序线程仍在运行时执行大部分跟踪和扫描工作,因此应用程序线程只能看到短暂的暂停.但是,如果并发收集器无法在终端生成填满之前完成回收无法访问的对象,或者如果无法满足终生代中可用空闲块的分配,则暂停应用程序并完成收集所有应用程序线程都停止了 无法同时完成收集称为并发模式失败,表示需要调整并发收集器参数.

稍后......

并发收集器在并发收集周期期间暂停应用程序两次.

我注意到那些GC似乎没有释放太多的内存.也许你的许多物品都是长寿的?您可能希望调整生成大小和其他GC参数.根据许多标准,10 Gig是一个巨大的堆,我天真地期望GC需要更长时间才能拥有如此庞大的堆.仍然,1秒是一个非常长的暂停时间,并指示出现问题(您的程序正在生成大量不需要的对象或生成难以回收的对象,或其他东西)或者您只需要调整GC.

通常,我会告诉别人如果他们必须调整GC,那么他们还有其他需要先解决的问题.但是,对于这种规模的应用,我认为你应该进入"需要比普通程序员更多地理解GC"的领域.

正如其他人所说,您需要分析您的应用程序以查看瓶颈所在.你的PermGen对于分配给它的空间来说太大了吗?你在创造不必要的物品吗?jconsole至少可以显示有关VM的最少信息.这是一个起点.然而,正如其他人指出的那样,您很可能需要比此更高级的工具.

祝好运.


eri*_*son 11

既然你提到了缓存的愿望,我猜你的大堆大部分都被缓存占用了.您可能希望限制缓存的大小,以确保它永远不会尝试增长到足以填充终生代.不要SoftReference单独依靠限制尺寸.由于旧一代填充了软引用,旧的引用将被清除并变成垃圾.将创建新的引用(可能是相同的信息),但由于可用空间短缺,因此可以快速清除.最终,终身空间充满垃圾,需要清理.

考虑调整-XX:NewRatio设置.默认值为1:2,表示堆的三分之一分配给新一代.对于大堆,这几乎总是太多.你可能想尝试像9这样的东西,这将为你的老一代保留9 Gb的10 Gb堆.


san*_*ity 6

事实证明,堆的一部分被交换到了磁盘,因此垃圾收集必须将一堆数据从磁盘中拉回内存.

我通过将Linux的"swappiness"参数设置为0来解决此问题(因此它不会将数据交换到磁盘).

  • 我意识到这是很久以前的事了,但对于未来的访问者:在这个规模的堆中,考虑为Java启用大页面.Hugepages也是不可交换的,因此它们将解决交换问题. (3认同)