我在拥有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,我的目标是在25ms内响应请求。不幸的是,我看到延迟超过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会导致线程被暂停,但这些似乎不是完整的GC。
可能需要注意的是,大多数内存都是由使用软引用的LRU内存缓存占用。
非常感谢任何帮助或建议。
答案 0 :(得分:11)
首先,查看Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning文档,如果您还没有这样做的话。该文档说:
并发收集器完成了大部分跟踪和扫描工作 应用程序线程仍在运行,因此只能看到短暂的暂停 应用程序线程但是,如果并发收集器无法完成 在终生代填满之前回收无法到达的对象,或者如果 无法满足分配中可用的空闲空间块 终身生成,然后暂停应用程序并完成收集 所有应用程序线程都停止了。无法完成收藏 并发被称为并发模式失败并表明需要 调整并发收集器参数。
稍后......
并发收集器在a期间暂停应用程序两次 并发收集周期。
我注意到那些GC似乎没有释放太多内存。也许你的许多物品都是长寿的?您可能希望调整生成大小和其他GC参数。 10 Gig是许多标准的巨大的堆,我天真地期望GC在这么大的堆上花费更长的时间。仍然,1秒是一个非常长的暂停时间,并指示出现问题(您的程序正在生成大量不需要的对象或生成难以回收的对象,或其他东西)或者您只需要调整GC。
通常,我会告诉别人如果他们必须调整GC,那么他们还有其他需要先解决的问题。但是对于这种规模的应用,我认为你会陷入“需要比普通程序员更多地了解GC。”
正如其他人所说,您需要分析您的应用程序以查看瓶颈所在。你的PermGen对于分配给它的空间来说太大了吗?你在创造不必要的物品吗? jconsole至少可以显示有关VM的最少信息。这是一个起点。然而,正如其他人指出的那样,您很可能需要比此更高级的工具。
祝你好运。答案 1 :(得分:11)
既然你提到了缓存的愿望,我猜你的大部分堆都被那个缓存占用了。您可能希望限制缓存的大小,以确保它永远不会尝试增长到足以填充终生代。不要仅依靠SoftReference
来限制大小。由于旧一代填充了软引用,旧的引用将被清除并变成垃圾。将创建新的引用(可能是相同的信息),但由于可用空间短缺,因此可以快速清除。最终,终身空间充满垃圾,需要清理。
考虑调整-XX:NewRatio
设置。默认值为1:2,表示堆的三分之一分配给新一代。对于大堆,这几乎总是太多。你可能想尝试9这样的东西,它可以为你的老一代保留9 Gb的10 Gb堆。
答案 2 :(得分:6)
原来,堆的一部分被换出到磁盘,因此垃圾收集必须将一堆数据从磁盘中拉回内存。
我通过将Linux的“swappiness”参数设置为0来解决这个问题(这样它就不会将数据交换到磁盘上)。
答案 3 :(得分:2)
以下是我发现的一些可能很重要的事情。
使用这些方法,RPC呼叫的延迟可以降低到低于200微秒,GC时间减少到1-3毫秒,影响不到1/300的呼叫。
答案 4 :(得分:0)
有些地方开始寻找:
此外,我通过分析器运行代码..我喜欢NetBeans中的代码,但也有其他代码。您可以实时查看gc行为。 Visual VM也是这样做的......但我还没有运行它(一直在寻找理由......但还没有时间或需要)。
答案 5 :(得分:0)
我还建议GCViewer和分析器。
答案 6 :(得分:0)
我希望能提供帮助的一些事情:
我从来没有对ConcurrentCollector好运,理论上它牺牲了吞吐量以减少延迟,但我发现吞吐量收集器的吞吐量和延迟(通过调整,以及我的应用程序)。
你的软参考缓存对于分代收藏家来说有点危险,可能是你年轻一代收藏品没有收集太多垃圾的原因之一。
如果我没有弄错的话,无论对象多么短暂,如果它被放入缓存(肯定会进入Tenured Generation),它将一直存在直到FullGC发生,甚至如果没有其他参考文献!
这意味着生活在缓存中的年轻人的对象现在被复制多次,保持活着状态,使他们的引用保持活跃状态,并且通常会减慢youngGen GC的速度。
有点矛盾的是,缓存如何减少对象分配,但会增加GC时间。
你可能还想尝试调整你的幸存者比率,它可能太小,甚至更多的“年轻”物体溢出到终身一代。
答案 7 :(得分: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 cheat sheet。