如何分析java中的内存碎片?

我们的服务器经历了几分钟的滞后。 可能它们是由“停止世界”垃圾收集引发的。 但是我们使用并发标记和扫描GC(-XX:+ UseConcMarkSweepG),所以,我认为,这些暂停是由旧一代的内存碎片触发的。

如何分析老一代的记忆碎片? 它有什么工具吗?

每小时就会发生一次陷阱。 大部分时间他们大约20秒,但有时 – 几分钟。

查看Java文档中的“java -X …”选项以打开GC日志记录。 这将告诉您是否正在收集旧的或新的一代,以及收集的时间。

暂停“几分钟”听起来非同寻常。 您确定不只是运行堆大小太小,或者在物理内存不足的机器上运行吗?

  • 如果您的堆太接近满,GC将一次又一次地被触发,导致您的服务器在GC中花费大部分CPU时间。 这将显示在GC日志中。

  • 如果在物理内存不足的计算机上使用大堆,则完整的GC可能会导致计算机“崩溃”,大部分时间花费大量时间将虚拟内存页面移入和移出光盘。 您可以使用系统监控工具观察到这一点; 例如,在典型的UNIX / Linux系统上观看“vmstat 5”的控制台输出。

跟进

与OP的观点相反,打开GC记录不太可能对性能产生明显的影响。

Oracle站点上的Understanding Concurrent Mark Sweep Garbage Collector Logs页面应该有助于解释GC日志。

最后,OP的结论是这是一个“碎片化”的问题是不太可能的,并且(IMO)没有得到他所提供的证据片段的支持。 它很可能是别的东西。

对于低级别监视,您将需要使用此-XX:PrintFLSStatistics=1 (或以更多阻塞成本将其设置为2)。 它没有记录,偶尔会给你一些统计数据。 不幸的是,由于不同的原因,它在大多数应用程序中并不是很有用,但它至少是有用的。

你应该能够看到例如

 Max Chunk Size: 215599441 

并将其与此进行比较

 Total Free Space: 219955840 

然后根据平均块大小和块数判断碎片。

我使用YourKit对这类问题有很好的效果。

维塔利,有碎片问题。 我的观察:如果对象的小尺寸经常更新,那么在这种情况下会产生大量垃圾。 虽然CMS收集这些对象占用的内存,但这个内存是碎片化的。 现在Mark-Sweep-Compact线程进入画面(停止世界)并试图压缩这个碎片化的内存导致长时间停顿。

与此相反,如果对象大小较大,则会产生较少碎片的内存和
Mark-Swap-Compact缩短了这个内存所需的时间。 这可能会导致吞吐量降低,但可以帮助您减少GC压缩造成的长时间停顿。

找出这个问题有点困难。 由于我花了一些时间在一个系统中找到并certificate这一点,让我列出发生这种情况的情景

  • 我们坚持使用Java 6,它没有任何压缩的垃圾收集器
  • 我们的应用程序做了太多GC主要是年轻一代的collections和一些老一代的collections
  • 我们的堆大小是一个非常大的主要问题(我们减少了,但我们的应用程序在太多的字符串和集合上花了很多钱)

显而易见的问题是我们系统中只有一个特定算法运行缓慢; 其余所有同时运行的,运行正常。 这排除了Full GC; 我们还使用jstat和其他j **工具来检查GC,线程转储+尾随GC日志。

从jstack线程转储,花了一段时间,我们可以知道哪个代码块真的在减速。 所以怀疑是堆积碎片。

为了测试我写了一个简单的程序,初始化了两个List一个ArrayList和一个LinkedList,并添加了导致resize的操作。 我可以通过REST句柄执行此测试。 通常没有太大区别。 但是在一个零碎的堆中,时间上看到了明显的差异; 使用ArrayList重新调整大集合变得非常慢,而不是使用Linked列表。 这些时间被记录下来,除了一个零碎的头部之外没有其他解释。

在Java 7中,我们转向了G1GC,以及GC调优和改进应用程序的大量工作; 这里堆压缩要好得多,它可以处理更大的堆,虽然我想任何超过16克的堆都会落在你不想去的地方 – GC suckage 🙂

要了解Vitaly如何处理此问题,请参阅了解并发标记扫描垃圾收集器日志 。

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

由于您没有看到高CPU利用率,因此也没有GC运行。 所以别的东西必然是你问题的原因。 以下是一些想法:

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

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

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

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

要了解GC日志的输出, 这篇文章可能有所帮助。

[编辑]我仍然无法理解“低CPU”和“GC档位”。 这两者通常相互矛盾。 如果GC停止运行,您必须看到100%的CPU使用率。 如果CPU空闲,则其他东西阻止了GC。 你有过载finalize()对象吗? 如果一个finalize阻止,GC可以永远。