10

我在具有 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)

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

java -XX:+UseConcMarkSweepGC -verbose:gc -server -Xmx10g -Xms10g ...

我的应用程序公开了一个 JSON-RPC API,我的目标是在 25 毫秒内响应请求。不幸的是,我看到延迟达到并超过 1 秒,这似乎是由垃圾收集引起的。以下是一些较长的示例:

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

这些垃圾收集事件中的每一个都伴随着延迟的 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]

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

另一件事是,我认为只有 FULL GC 会导致线程暂停,但这些似乎不是 full GC。

可能需要注意的是,大部分内存都被使用软引用的 LRU 内存缓存占用。

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

4

7 回答 7

11

首先,如果您还没有这样做,请查看Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning文档。该文档说:

并发收集器在应用程序线程仍在运行的情况下完成大部分跟踪和清扫工作,因此应用程序线程只会看到短暂的暂停。但是,如果并发收集器无法在年老代填满之前完成对不可达对象的回收,或者如果在年老代中的可用空闲空间块无法满足分配,则应用程序将暂停并完成收集所有应用程序线程都停止了。无法同时完成收集称为并发模式故障,表示需要调整并发收集器参数。

稍后...

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

我注意到那些 GC 似乎并没有释放太多内存。也许你的许多物品都是长寿的?您可能希望调整生成大小和其他 GC 参数。从许多标准来看, 10 Gig 是一个巨大的堆,我天真地期望 GC 需要更长的时间来处理如此巨大的堆。尽管如此,1 秒是一个很长的停顿时间,它表明要么有问题(你的程序正在生成大量不需要的对象,要么正在生成难以回收的对象,或者其他什么),要么你只需要调整 GC。

通常,我会告诉某人,如果他们必须调整 GC,那么他们还有其他问题需要先解决。但是对于这种规模的应用程序,我认为你落入了“需要比普通程序员更了解 GC”的境地。

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

祝你好运。

于 2009-02-22T01:59:06.540 回答
11

既然您提到了缓存的愿望,我猜您的大部分巨大堆都被该缓存占用了。您可能希望限制缓存的大小,以确保它永远不会尝试增长到足以填满终身代。不要依靠SoftReference单独来限制大小。当老一代被软引用填充时,旧的引用将被清除并成为垃圾。将创建新的引用(可能是相同的信息),但会很快清除,因为可用空间不足。最终,永久空间充满了垃圾,需要清理。

也考虑调整-XX:NewRatio设置。默认为 1:2,意味着三分之一的堆分配给新生代。对于一个大堆来说,这几乎总是太多了。您可能想尝试类似 9 的方法,它可以为老一代保留 10 Gb 堆中的 9 Gb。

于 2009-02-22T03:14:01.213 回答
6

结果发现堆的一部分被换出到磁盘,因此垃圾收集必须将一堆数据从磁盘拉回内存。

我通过将 Linux 的“swappiness”参数设置为 0 来解决这个问题(这样它就不会将数据交换到磁盘上)。

于 2009-03-06T23:39:18.843 回答
2

以下是我发现的一些可能很重要的事情。

  • JSON-RPC 可以生成很多对象。没有 XML-RPC 那么多,但仍然值得关注。在任何情况下,您确实似乎每秒生成 100 MB 的对象,这意味着您的 GC 运行的时间比例很高,并且可能会增加您的随机延迟。即使 GC 是并发的,您的硬件/操作系统也很可能在负载下表现出非理想的随机延迟。
  • 看看你的记忆库架构。在 Linux 上,命令是 numactl --hardware。如果您的 VM 被拆分到多个内存库中,这将显着增加您的 GC 时间。(它也会减慢您的应用程序,因为这些访问的效率可能会大大降低)您对内存子系统的工作越努力,操作系统就越有可能不得不移动内存(通常是大量),结果您会出现剧烈的停顿( 100 毫秒并不奇怪)。不要忘记您的操作系统不仅仅是运行您的应用程序。
  • 考虑压缩/减少缓存的内存消耗。如果您正在使用多 GB 的缓存,则值得研究比现有方法进一步减少内存消耗的方法。
  • 我建议您同时使用内存分配跟踪和 cpu 采样来分析您的应用程序。这可能会产生非常不同的结果,并且通常会指出这些问题的原因。

使用这些方法,可以将 RPC 调用的延迟降低到200 微秒以下,并将 GC 时间降低到 1-3 毫秒,影响不到 1/300 的调用。

于 2009-02-22T03:10:38.490 回答
0

一些开始寻找的地方:

此外,我会通过探查器运行代码。我喜欢 NetBeans 中的代码,但也有其他代码。您可以实时查看 gc 行为。Visual VM 也可以做到这一点......但我还没有运行它(一直在寻找一个理由......但还没有时间或需要)。

于 2009-02-22T00:47:01.207 回答
0

我还建议使用 GCViewer和分析器。

于 2009-02-22T00:53:23.983 回答
0

我个人没有使用过这么大的堆,但我在使用 Oracle/Sun Java 1.6.x 的以下开关时通常经历了非常低的延迟:

-Xincgc -XX:+UseConcMarkSweepGC -XX:CMSIncrementalSafetyFactor=50
-XX:+UseParNewGC
-XX:+CMSConcurrentMTEnabled -XX:ConcGCThreads=2 -XX:ParallelGCThreads=2
-XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=5
-XX:GCTimeRatio=90 -XX:MaxGCPauseMillis=20 -XX:GCPauseIntervalMillis=1000

在我看来,重要的部分是在老年代使用 CMS,在年轻一代中使用 ParNewGC。此外,这为 CMS 增加了一个相当大的安全系数(默认为 10% 而不是 50%)并要求较短的暂停时间。由于您的目标是 25 毫秒响应时间,我会尝试设置-XX:MaxGCPauseMillis为更小的值。您甚至可以尝试使用两个以上的内核进行并发 GC,但我这不值得 CPU 使用。

您可能还应该查看HotSpot JVM GC 备忘单

于 2012-01-17T09:07:01.587 回答