运行几个小时后,JVM垃圾收集器突然消耗100%的CPU

时间:2016-02-08 04:03:21

标签: java clojure garbage-collection jvm java-websocket

我的Clojure应用程序中有一个奇怪的问题。

我正在使用http-kit编写基于websocket的聊天应用程序。

使用React作为单页面应用程序呈现客户端,当他们导航到主页(登录后)时,他们首先要创建一个websocket来接收实时更新和任何聊天消息等内容。你可以在这里看到这个网站:www.csgoteamfinder.com

我遇到的问题是经过一段不确定的时间,重启后30分钟甚至48小时,运行聊天服务器的JVM突然开始消耗所有CPU。当我用NR(New Relic)检查它时,我可以看到垃圾收集器正在使用所有这些时间 - 在这个阶段我不知道它在做什么。

我已经拍了很多截图,你可以看到效果。

New Relic showing memory usage over a 7 day period

More New Relic statistics

您可以看到许多峰值,这些峰值对应于因垃圾收集器而导致的CPU使用率大幅增加。为了释放CPU,我通常必须重新启动JVM,我一直依赖于在我的松弛帐户中从NR接收CPU警报,以确保我快速跳过这些....但我真的需要找到它的根源问题

我最初的想法是,当客户端在结束时关闭它时,我可能会持有套接字引用,但事实并非如此。我一直在定期查看套接字计数,它相当稳定。

任何从哪里开始的想法?

亲切的问候,杰森。

4 个答案:

答案 0 :(得分:3)

很难想象会出现什么问题。但首先我要做的是在崩溃时进行堆转储。可以使用-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<path_to_your_heap_dump> JVM args启用此功能。 作为一般做法,不要将堆大小增加到服务器计算机上可用的物理内存大小。在极少数情况下,JVM无法转储堆空间,因为进程注定失败;在这种情况下,你可以使用gcore(如果你在Linux上,不确定Windows)。

一旦你抓住堆转储,用mat分析它,我已经调试了这样的应用程序,这完美地解决了任何与内存相关的问题。 Mat允许您深入剖析堆转储,以便在不分配非常小的堆空间的情况下确保找到内存问题的原因。

答案 1 :(得分:2)

如果你的程序在垃圾收集中花费了大量的CPU时间,那意味着你的堆已经满了。通常这意味着两件事之一:

  1. 您需要为您的程序分配更多堆(通过-Xmx)。
  2. 您的程序正在泄漏内存。
  3. 首先尝试前者。为您的程序分配大量内存(根据您正在查看的图表,在您的情况下为16GB或更多)。看看你是否还有相同的症状。

    如果症状消失,那么你的程序只需要更多的记忆。否则,您有内存泄漏。在这种情况下,您需要进行一些内存分析。在JVM中,通常这样做的方法是使用jmap生成堆转储,然后使用堆转储分析器(例如jhat或VisualVM)来查看它。

    (公平披露:我是名为fasthat的jhat fork的创建者。)

答案 2 :(得分:2)

很可能你的任期空间正在填补触发完整的收藏。此时GC会在一段时间内使用所有CPUS。

要诊断出现这种情况的原因,您需要查看您的晋升率(从年轻一代到终身空间的数据量)

我会考虑增加年轻一代,以降低晋升率。您还可以查看使用CMS,因为它具有更短的暂停时间(尽管它使用更多CPU)

答案 3 :(得分:1)

要按顺序尝试:

  1. 减小堆大小
  2. 计算每个班级的对象数量,并查看数字是否合理
  3. 你有超过第1代的大字节[]吗?
  4. 更改或调整GC算法
  5. 使用高可用性,即多个JVM
  6. 切换到Erlang
  7. 您已触发全球GC。 GC时间的增长速度快于线性,具体取决于内存量,因此实际减少堆空间将更频繁地触发全局GC并使其更快。

    您还可以尝试更改GC算法。我们有一个系统,全球GC从200s(每24小时发生1-2次)下降到12s。是的,系统完全静止了3分钟,没有用户不高兴:-)你可以尝试-XX:+ UseConcMarkSweepGC

    http://www.fasterj.com/articles/oraclecollectors1.shtml

    对于JVM和类似的人,你总会有这样的停顿;它更多的是关于你获得它的频率,以及全球GC的速度。您应该进行堆转储并获取每个类的不同对象的计数。最有可能的是,你会看到你有数以百万计的其中一个,不知何故,你在不断增长的缓存或会话或类似的东西中保持指向它们的指针。

    http://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks001.html#CIHCAEIH

    您还可以开始使用至少包含2个节点的高可用性解决方案,这样当一个节点忙于执行GC时,另一个节点将不得不在一段时间内处理总负载。希望您不会同时在两个系统上获得全局GC。

    像byte []和类似的大二进制对象是一个真正的问题。你有那些吗?

    在某些时候,这些需要由全球GC压缩,这是一个缓慢的操作。许多基于数据处理JVM的解决方案实际上避免将所有数据作为普通POJO存储在堆上,并自行实现堆栈以克服此问题。

    另一种解决方案是从JVM切换到Erlang。 Erlang几乎是实时的,他们没有整个堆的全局GC的概念。 Erlang有很多小堆。你可以在

    上阅读一些相关内容

    https://hamidreza-s.github.io/erlang%20garbage%20collection%20memory%20layout%20soft%20realtime/2015/08/24/erlang-garbage-collection-details-and-why-it-matters.html

    Erlang比JVM慢,因为它复制数据,但性能更加可预测。两者都很难。我有一个基于Webocket的基于Erlang的解决方案,它确实运行良好。

    因此,您遇到了JVM,Microsoft CLR等类似的预期和正常问题。在堆大小增加的情况下,它将在接下来的几年中变得更糟,更常见。