如何分析Java中的内存碎片问题?

13
我们的服务器出现了几分钟的滞后。可能是由于“停止世界”垃圾回收引起的。但是我们使用了并发标记和清除GC(-XX:+UseConcMarkSweepG),所以我认为这些暂停是由老年代内存碎片化触发的。
如何分析老年代内存碎片化?是否有相关工具可用?
滞后情况每小时发生一次。大多数时间它们持续约20秒,但有时会持续几分钟。

1
你确定是垃圾回收(GC)引起的吗?特别是,在暂停期间,vm进程是否显示高CPU利用率?如果不是,我认为你在某个地方存在竞争条件。 - Esko
这不是竞态条件,我确定。在分析器中进行了检查,我们几乎不使用锁,并且在不同的服务器上行为是相同的 - 只是延迟时间不同。而且CPU利用率不高 - 应用程序中的所有线程都已停止。 - Vitaly
在打开GC日志后,我发现了“晋升失败”的问题。这里有一个很好的描述:http://www.sun.com/bigadmin/content/submitted/cms_gc_logs.html。感谢大家的帮助。 - Vitaly
7个回答

7
请查看Java文档中有关“java -X…”选项以打开GC日志记录。这将告诉您是否正在收集旧的或新的生成,以及收集需要多长时间。
“几分钟”的暂停听起来很不寻常。您确定您没有使用过小的堆大小或在内存不足的机器上运行?
  • 如果堆接近饱和,GC将被触发并且重复出现,导致服务器大部分CPU时间都花费在GC上。这将显示在GC日志中。
  • 如果在内存不足的机器上使用大型堆,则完整GC可能会导致您的机器“抖动”,大部分时间都在疯狂地移动虚拟内存页面到磁盘。您可以使用系统监控工具来观察此情况;例如,在典型的UNIX / Linux系统上通过观察“vmstat 5”的控制台输出。
后续 与OP的看法相反,打开GC日志记录不太可能对性能产生明显影响。

Oracle网站上的了解并发标记清除垃圾收集器日志页面应该有助于解释GC日志。

最后,楼主得出这是一个“碎片化”问题的结论是不太可能的,并且(在我看来)不受他提供的证据片段支持。很可能是其他原因。


一天之中可能会出现几分钟的延迟。编辑了一个问题。 - Vitaly
是的,我会尝试冗长的GC输出。它只是打印太多信息-可能会减慢服务器速度,不想这样做 :) 现在我们使用GarbageCollectorMXBeans。输出看起来像这样:ConcurrentMarkSweep 27459。延迟几乎完美地匹配它(27秒)。每隔一小时左右会发生一次,这就是为什么我认为是内存碎片而不是内存泄漏的原因。– Vitaly 0秒前 [删除此评论] - Vitaly

3

如果您需要进行低级监控,您需要使用-XX:PrintFLSStatistics=1(或者将其设置为2以获得更多的阻塞成本)。它是未记录的,有时会给出一些统计数据。不幸的是,由于不同的原因,它在大多数应用程序中并不是非常有用,但至少可以提供参考。

例如,您应该能够看到:

Max Chunk Size: 215599441

并将其与此进行比较

Total Free Space: 219955840

然后根据块的平均大小和数量来判断分段情况。

0

这是一个比较难找到的问题。因为我花了一些时间在系统中找到并证明,让我列出发生这种情况的场景。

  • 我们被困在使用没有压缩垃圾收集器的Java 6中
  • 我们的应用程序进行了太多的GC,主要是年轻代收集和一些大的老年代收集
  • 我们的堆大小相当大-主要问题(我们减少了,但我们的应用程序正在消耗太多的字符串和集合)

表现出来的问题是,我们系统中只有一个特定的算法运行缓慢;同时运行的其他所有算法都正常运行。这排除了Full GC;此外,我们使用jstat和其他j **工具来检查GC、线程转储+跟踪GC日志。

从一段时间内获取的jstack线程转储中,我们可以得到一个想法,哪个代码块真正地减慢了速度。所以怀疑落在了堆碎片上。

为了测试,我编写了一个简单的程序,初始化了两个列表,一个是ArrayList,另一个是LinkedList,并进行了添加操作以引起调整大小。我可以通过REST处理程序执行此测试。 通常没有太大的区别。但在碎片化堆内部,时间上有明显的差异;使用ArrayList进行大型集合调整大小比使用LinkedList要慢得多。这些时间已被记录下来,除了碎片化头之外,没有其他解释。
在Java 7中,我们转向G1GC,同时进行了大量的GC调优和应用程序改进;这里堆压缩更好,可以处理更大的堆,尽管我猜超过16g堆会让你陷入不想去的地方- GC suckage :)

0
Vitaly,有一个碎片化问题。我的观察是:如果有一些小的对象需要频繁更新,那么就会产生大量垃圾。虽然CMS收集了这些对象占用的内存,但这些内存是碎片化的。现在Mark-Sweep-Compact线程介入(停止整个系统),试图压缩这些碎片化的内存,导致长时间暂停。
相反,如果对象的大小更大,则会生成较少的碎片化内存,并且Mark-Swap-Compact需要较少的时间来压缩此内存。这可能会导致吞吐量降低,但可以帮助您减少GC压缩引起的长时间暂停。

我们已经解决了这个问题。有时候老年代中没有足够的内存来复制从年轻代幸存下来的对象。当固定数量的内存被消耗时,启动CMS可以解决这个问题。 - Vitaly
Vitaly,请您简要指出您是如何解决碎片化问题的方式? 您是如何在消耗固定金额后触发CMS的?这又是如何解决碎片化问题的呢? - user174334

0

我曾经使用YourKit来解决这种类型的问题,效果很好。


1
是的,这是一款很棒的工具。但它不显示内存碎片 - 只显示内存消耗情况,对于卡顿问题并没有帮助。或者我不知道有什么很酷的选项? - Vitaly
YourKit和其他内存分析工具将向您展示GC何时/多频繁地尝试重新组织内存并减少碎片化。它不会直接显示您的碎片化情况(除非我还不知道某个很酷的选项)。 - Eric J.
我将通过VisualVM检查GC的行为。顺便说一下,YourKit在生产服务器上运行是很危险的。即使选择了“禁用所有”选项,我们也遇到了性能问题。感谢您关于大对象的建议。 - Vitaly
你们遇到了什么性能问题?到目前为止,我们只在压力测试环境中使用过它,但考虑在一个应用服务器上运行一段时间以收集实际指标。 - Eric J.
如果打开了字节码插装(代理的默认设置),则延迟非常大。如果关闭,则只有延迟。 - Vitaly

-1

-4

Java中不存在内存碎片; 在GC运行期间,内存区域会被压缩。

由于您没有看到高CPU利用率,因此也没有GC运行。因此,其他原因必须是导致问题的原因。以下是一些想法:

  • 如果应用程序的数据库位于不同的服务器上,则可能存在网络问题

  • 如果您运行Windows并且已映射网络驱动器,则其中一个驱动器可能会锁定计算机(再次是网络问题)。对于Unix上的NFS驱动器也是如此。检查系统日志以获取网络错误。

  • 计算机是否将大量数据交换到磁盘?由于CPU利用率低,问题的原因可能是应用程序被交换到磁盘,而GC运行强制将其推回RAM。如果您的服务器没有足够的真实RAM来保持整个Java应用程序在RAM中,则这将需要很长时间。

此外,其他进程可能会强制将应用程序移出RAM。检查实际内存利用率和交换空间使用情况。

要理解GC日志的输出,this post 可能会有所帮助。

[编辑] 我仍然无法理解“低CPU”和“GC停顿”。这两者通常是相互矛盾的。如果GC停顿,您必须看到100%的CPU使用率。如果CPU处于空闲状态,则其他某些内容会阻止GC。您是否有超载finalize()的对象?如果finalize阻塞,则GC可能需要很长时间。


Eric J.,大对象听起来是个好主意,谢谢。我会检查内存转储。 - Vitaly
不,这不是网络问题。GC报告的延迟与我们内部日志中的延迟相匹配。 - Vitaly
@Vitaly:请提供更多关于你的应用程序如何工作的信息。它是一直在创建巨大的HashMaps或者类似的东西(即具有对其他对象的大量引用的大型数据图)吗? - Aaron Digulla
@Aaron 关于哈希映射。我们有一些大小为20-30K元素的HashMap(不确定它们是否很大)。此外,我们经常创建相当大的byte[] - 它们可以是40K-80K的大小。 - Vitaly
byte[] 对于 GC 来说并不重要;对于 GC,只有对象引用才重要。你一直在 HashMap 中添加/删除元素吗?至于“所有线程都被阻塞”,那应该是“除了 GC 线程以外的所有线程”。而 GC 线程会尽可能地占用 CPU,因此在运行时,您必须拥有 100% 的 CPU 使用率,否则就有些奇怪了。 - Aaron Digulla
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接