Scala actor应用程序的奇怪GC行为

oxb*_*kes 9 garbage-collection jvm scala out-of-memory actor

我有一个使用相当多的演员的应用程序:准确地说是25,000.它使用Scala 2.7.7并在jdk6_u18上运行.它基本上监听和处理市场数据,并且几乎没有状态.

它从每天上午8点02分开始,并在一小时内崩溃了OutOfMemoryError."啊哈"你说,"你有内存泄漏!" 除了当我重新启动它说,它从来没有,永远再次崩溃的一天的休息!尽管美国市场在下午2:30开放,但GC和CPU开销都有所上升.

一些轶事发现:

  • 它在Solaris上运行.当我用在Linux上运行它,它永远不会崩溃可言.
  • 我已经尝试搞乱代堆大小调整,分配更多内存等等.我认为没有区别
  • 当我verbose:gc打开时,收集器的行为似乎有所不同

一些问题出现了:

  1. 为什么Linux和Solaris之间的程序行为会有所不同?
  2. 为什么在8.02开始和8.42开始之间的行为会有所不同?
  3. 我听说演员库有一些内存泄漏问题.他们是什么时候,他们什么时候修好了,我怎么能发现这里有什么"相似"的东西?(在jhat中寻找的东西等)
  4. 有没有人知道可能会发生什么?

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

G1的一些输出带有详细:gc on

我想我刚刚抓住了这个行为:

600.290:[全GC 255M-> 144M(256M),1.5772616秒]
602.084:[GC暂停(年轻)227M-> 145M(256M),0.0556769秒]
602.418:[全GC 254M-> 144M(256M),1.6415216秒]
604.279:[GC暂停(年轻)227M-> 145M(256M),0.0415157秒]
604.602:[全GC 255M-> 145M(256M),1.6041762秒]
606.422:[GC暂停(年轻)227M-> 145M(256M) ),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-> 176M(256M),1.9352357秒]
853.492:[GC暂停(年轻)228M-> 176M(256M),0.0224688秒]
853.716:[全GC 254M-> 176M(256M),1.9339705秒]
855.793:[GC暂停(年轻)228M-> 176M(256M) ),0.0215707秒]
856.009:[全GC 254M-> 176M(256M),1.9805797秒]
858.137:[GC暂停(年轻)228M-> 176M(256M),0.0223224秒]

G1的一些输出带有详细信息:gc off

再好了!*叹*

303.656:[GC暂停(年轻)225M-> 93M(256M),0.1680767秒]
308.060:[GC暂停(年轻)226M-> 94M(256M),0.1793724秒]
312.746:[GC暂停(年轻)227M-> 93M (256M),0.1674851秒]
316.162:[GC暂停(年轻)227M-> 95M(256M),0.1826145秒]
320.147:[GC暂停(年轻)226M-> 94M(256M),0.1656664秒]
325.978:[GC暂停(年轻)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-> 125M (256M),0.1851359秒]
25889.940:[GC暂停(年轻)223M-> 126M(256M),0.2046496秒]
25891.567:[GC暂停(年轻)224M-> 126M(256M),0.1600574秒]

然后,还有一个完整的GC

37180.191:[GC暂停(年轻)225M-> 154M(256M),0.1716404秒]
37182.163:[GC暂停(年轻)(初始标记)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.2043307]
37183.393:[ GC净化198M-> 198M(256M),0.0068127秒]
37183.400:[GC concurrent-cleanup-start]
37183.400:[GC concurrent-cleanup-end,0.0000393]
37183.648:[GC暂停(年轻)222M-> 153M(256M) ,0.1483041秒]
37184.235:[GC暂停(部分)171M-> 91M(256M),0.2520714秒]
37187.223:[GC暂停(年轻)221M-> 92M(256M),0.1721220秒]

UPDATE

那么,既然切换到G1垃圾收集器上jdk1.6.0_18应用已经表现本身上3个连续天.我怀疑Erik对于虚拟机在高吞吐量情况下陷入死亡螺旋的情况的分析是正确的,在这种情况下,它将对象推广到终身代.

Eri*_*cht 4

您是否有理由期望堆的大小会缓慢增长?两条痕迹看起来都在增长。我在很多情况下做过的一件事就是减少堆,试图让问题变得更糟。然而,256M 大约是 Scala 的下限。

我之前注意到的一件事是,如果你有一些短命的对象,由于太大的压力而从伊甸园生成出来,它会逐渐杀死你。当有大量活动(也许您早上有大量活动?)并且您的伊甸园空间不够大时,就会发生这种情况。一般而言,Scala,尤其是演员,大量使用短暂的小物体,似乎有一个神奇的门槛,一旦你跨过,一切都会走下坡路。因此,一次运行会很好,但下一次就会崩溃并烧毁。

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

这就带来了另一件事——确保调度程序不会随意创建新线程。有时它会这样做。我想说你几乎应该总是手动设置线程的上限。我不知道它有多相关,但是关于 Actor 默认使用的 fork-join 调度程序的一个有趣的事实是,它适用于短期的、CPU 密集型任务。在它管理的线程中进行 IO 会破坏它的假设。当然它应该仍然有效,但是......

祝你好运!我已经因为这些问题而失去了很多很多日子。

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

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

-XX:+UseParallelGC
Run Code Online (Sandbox Code Playgroud)