9

我有一个应用程序使用了很多演员:准确地说是 25,000。它使用Scala 2.7.7 并在jdk6_u18上运行。它基本上是监听和处理市场数据,状态很少。

它每天早上 8 点 2 点开始,一个小时内就崩溃了OutOfMemoryError。“啊哈”你说,“你有内存泄漏!” 除了当我重新启动它时,它永远不会一天的剩余时间内再次崩溃!尽管美国市场在下午 2.30 开放时,GC 和 CPU 开销都增加了。

一些轶事发现:

  • 它在 Solaris 上运行。当我过去在 Linux 上运行它时,它从来没有崩溃
  • 我试过搞乱代堆大小,分配更多内存等。我认为这没什么区别
  • 当我verbose:gc打开时,收集器的行为似乎有所不同

提出了几个问题:

  1. 为什么这个程序的行为在 Linux 和 Solaris 之间会有所不同?
  2. 为什么从 8.02 开始和从 8.42 开始的行为会有所不同?
  3. 我听说演员库有一些内存泄漏问题。它们是什么,什么时候修复的,我如何发现这里是否发生了“类似”的事情?(在jhat等中寻找的东西)
  4. 有谁知道可能会发生什么?

我现在正试图G1看看这是否有什么不同。我明天会更新这个问题!

G1 的一些输出,带有 verbose:gc on

我想我只是在行动中抓住了它:

600.290:[完全 GC 255M->144M(256M),1.5772616 秒]
602.084:[GC 暂停(年轻)227M->145M(256M),0.0556769 秒]
602.418:[完全 GC 254M->1441526 秒,1. ]
604.279:[GC 暂停(年轻)227M->145M(256M),0.0415157 秒]
604.602:[完全 GC 255M->145M(256M),1.6041762 秒]
606.422:[GC 暂停(年轻)227M->145M(256 ), 0.0237441 秒]
606.710: [完整 GC 254M->145M(256M), 1.6022185 秒]

然后稍晚一点(你可以看到完整的 GC 花费了更长的时间并且回收的更少)

849.084: [完全 GC 254M->176M(256M), 1.9658754 秒]
851.191: [GC 暂停 (年轻) 228M->176M(256M), 0.0218611 秒]
851.414: [完全 GC 254M->1765257 秒, 1. ]
853.492:[GC 暂停(年轻)228M->176M(256M),0.0224688 秒]
853.716:[完全 GC 254M->176M(256M),1.9339705 秒]
855.793:[GC 暂停(年轻)228M->176M(256 ), 0.0215707 秒]
856.009: [完全 GC 254M->176M(256M), 1.9805797 秒]
858.137: [GC 暂停 (年轻) 228M->176M(256M), 0.0223224 秒]

G1 的一些输出,详细信息:gc 关闭

又好了!*叹*

303.656:[GC 暂停(年轻)225M->93M(256M),0.1680767 秒]
308.060:[GC 暂停(年轻)226M->94M(256M),0.1793724 秒]
312.746:[GC 暂停(年轻)227M->93M (256M), 0.1674851 secs]
316.162: [GC pause (young) 227M->95M(256M), 0.1826145 secs]
320.147: [GC pause (young) 226M->94M(256M), 0.1656664 secs]
325.978: [GC pause (年轻) 226M->93M(256M), 0.1475760 秒]
330.176: [GC 暂停 (年轻) 226M->94M(256M), 0.1727795 秒]

很久很久以后它仍然可以!

25882.894:[GC 暂停(年轻)224M->125M(256M),0.2126515 秒]
25884.880:[GC 暂停(年轻)224M->126M(256M),0.2059802 秒]
25887.027:[GC 暂停(年轻)224M->12 (256M), 0.1851359 秒]
25889.940: [GC 暂停 (年轻) 223M->126M(256M), 0.2046496 秒]
25891.567: [GC 暂停 (年轻) 224M->126M(256M), 0.1600574 秒]

再后来,一个完整的 GC

37180.191: [GC pause (young) 225M->154M(256M), 0.1716404 secs]
37182.163: [GC pause (young) (initial-mark) 225M->153M(256M)37182.326: [GC concurrent-mark-start], 0.1622246 secs]
37183.089: [GC concurrent-mark-end, 0.7635219 sec]
37183.090: [GC remark, 0.0032547 secs]
37183.093: [GC concurrent-count-start]
37183.297: [GC concurrent-count-end, 0.20433307]
3.2043307 ] GC 清理 198M->198M(256M), 0.0068127 秒]
37183.400: [GC concurrent-cleanup-start]
37183.400: [GC concurrent-cleanup-end, 0.0000393]
37183.648: [GC pause (young) 222M->153M(256M) , 0.1483041 秒]
37184.235: [GC 暂停 (部分) 171M->91M(256M), 0.2520714 秒]
37187.223:[GC 暂停(年轻)221M->92M(256M),0.1721220 秒]

更新

好吧,自从在 jdk1.6.0_18 上切换到 G1 垃圾收集器后,应用程序已经连续三天表现良好。我怀疑 Erik 对 VM在高吞吐量情况下将自己陷入死亡螺旋的情况的分析是正确的,因为它已经将对象提升到了终身代。

4

3 回答 3

4

您有任何理由期望您的堆大小会慢慢增长吗?看起来它在两个痕迹中都在增长。我在很多场合做过的一件事是减少我的堆以试图使问题变得更糟。但是,256M 大约是 Scala 的下限。

我之前注意到的一件事是,如果你的生命周期很短的对象由于压力太大而脱离了伊甸园一代,它可能会逐渐杀死你。当活动爆发时(也许您早上爆发了?)并且您的伊甸园空间不够大,就会发生这种情况。Scala 尤其是演员大量使用短命的小对象,似乎有一个神奇的门槛,一旦你越过它就会下山。所以一次运行会很好,下一次会崩溃和烧毁。

我之前注意到的另一件事是,在 OSX/x86 上运行良好的 GC 设置通常在 Sparc/Solaris 上运行不佳,反之亦然。如果您使用的是 CoolThreads,我建议您将 GC 配置为调度程序/池中的每个线程有一个 GC 线程。

这带来了另一件事 - 确保调度程序不会随意创建新线程。它有时会这样做。我会说你应该几乎总是手动设置线程的上限。我不知道它的相关性如何,但是关于参与者默认使用的 fork-join 调度程序的一个有趣事实是,它适用于短的、受 CPU 限制的任务。在它管理的线程中执行 IO 会破坏它的假设。当然它应该仍然可以工作,但是......

祝你好运!我已经为这些问题失去了很多很多天。

看看这里的一些选项:http: //java.sun.com/performance/reference/whitepapers/tuning.html

看起来您正在使用并发标记扫描收集器。尝试设置:

-XX:+UseParallelGC
于 2010-02-03T03:41:13.103 回答
1

在这一点上,我想知道是否值得尝试用 Lift 或 Akka 替换您的演员?尽管我认为这不太可能是它们的错误,但它们可能不会对导致崩溃的任何东西发痒。

于 2010-02-02T13:00:07.533 回答
0

从我的角度来看,堆布局是错误的。老空间从一开始的93M到高负载的176不等。正如我从日志中看到的那样,您的生成平均约为 50mb/秒。因此,如果您在 176mb 处的全 gc 上暂停了大约 2 秒,那么您将没有年轻的空间来创建新对象。我的建议:

  • 检查幸存者空间设置 - 您的旧空间增长到 176M。减少新空间——显然,这不是故意的
  • 将 NewSize 显式设置为所需值,例如 128M

  • 增加整体堆大小以提升对象而不是执行完整的 gc

  • 你有很长的停顿:gc 80mb>50 ms,~120mb>150ms。改用 CMS,我相信它会更快。(但做长凳)
于 2011-09-24T21:29:41.023 回答