Tomcat 间歇性减速、无响应、挂起

Tomcat 间歇性减速、无响应、挂起

我一直在尝试找出 Tomcat 服务器间歇性减速的原因。我们每周都会遇到几次减速,Tomcat 会停止响应或需要几分钟来处理请求,并且 (Linux) 机器上的 CPU 负载(如正常运行时间所示)会从通常的 1-2 左右飙升至 30 以上。然后事情逐渐好转,大约 10 分钟后一切都恢复正常。

我们使用 Apache 作为前端,使用 Postgres 作为数据库。我一直在研究日志,试图找出问题的原因。在速度变慢的时候,我没有注意到需求有任何明显的增加。

我发现,在速度变慢之前,Tomcat 多次进入休眠状态,大约持续三分半钟。在此期间,Tomcat 的日志中没有任何条目,也没有对数据库的查询。在短暂的休眠之后,Tomcat 会醒来并疯狂地开始尝试处理在此期间备份的所有内容,从而导致数据库和 CPU 负载过重,响应时间变慢。

为了弄清楚 Tomcat 在休眠期间都在做什么,我设置了一个脚本来监视它的日志,如果日志中三分钟内没有活动,则发送 kill -3 信号以获取线程转储。不幸的是,该信号不会唤醒 Tomcat,因此线程转储直到它自行唤醒并恢复处理后才会发生。

Apache 和 Postgres 在这三分半钟的间隙期间显然仍处于唤醒和活动状态 - 它们的日志显示在此期间非 Tomcat 相关的活动仍在继续。

我们的Tomcat版本是5.0.28。

有什么想法或建议吗?我对 Tomcat 还很陌生,所以请不要认为我了解很多。


按照 Alex 的建议激活详细垃圾收集后,我捕获了几次问题发生的情况,发现是由完整 GC 引起的,在两种情况下都耗时超过 200 秒,例如:

04:21:55.648491500 [GC 1035796K->933637K(1041984K), 0.3407580 secs]
04:21:56.012832500 [Full GC[Unloading class sun.reflect.GeneratedMethodAccessor633]
04:22:38.003920500 [Unloading class sun.reflect.GeneratedSerializationConstructorAccessor39]
04:22:38.004051500 [Unloading class sun.reflect.GeneratedConstructorAccessor102]
04:22:38.004392500 [Unloading class sun.reflect.GeneratedConstructorAccessor98]
04:22:38.004533500 [Unloading class sun.reflect.GeneratedSerializationConstructorAccessor40]
04:22:38.004716500 [Unloading class sun.reflect.GeneratedMethodAccessor634]
04:22:38.004808500 [Unloading class sun.reflect.GeneratedConstructorAccessor90]
04:22:38.004889500 [Unloading class sun.reflect.GeneratedConstructorAccessor95]
04:22:38.005044500 [Unloading class sun.reflect.GeneratedMethodAccessor632]
04:25:18.688916500  933637K->154281K(1041984K), 202.6760940 secs]

现在我只需要弄清楚如何调整以防止这种情况发生。(欢迎提出建议。)

感谢 Alex 和 Mainguy 的帮助。

答案1

第一步,如上所述,是更改 tomcat 启动脚本以添加

-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails  

当你放慢脚步时,寻找catalina.out比如“FullGC”或者多次 GC......

需要注意的是,如果您还没有这样做,请将 tomcat 堆大小增加到大约 1/2 到 3/4 可用内存(假设此框仅运行 tomcat)。例如,要将最大堆设置为 768 兆字节,您需要添加:

-Xmx768M

JAVA_OPTS

如果您使用的是 ubuntu 10.04,这些设置通常位于 /etc/default/tomcat6。

答案2

我们曾经遇到过这种情况,当 Java 堆的“老年代”内存中的大部分内存被换出到磁盘时,因为它们是垃圾,而且很长时间没有使用。当需要进行完整收集时,必须将该内存换回。

在这种情况下,您的答案有点违反直觉:减少 Java 堆的大小,或者找出导致交换的其他内存使用情况。在我们的例子中,一些夜间批处理作业使用了大量内存,导致旧代被交换到磁盘。因此,第二天早上需要的第一次完整 GC 花费了很长时间(180 多秒,就像您看到的那样)。

您还可以尝试并发标记清除收集器,它通过并行执行大量工作来减少完整 GC 时间。这是我见过的最好的文档;此外还有一些关于这个主题的优秀 Sun 博客: http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html

答案3

尝试激活详细垃圾收集,看看是否是垃圾收集暂停。我猜想,一个巨大的堆、大量的对象分配和交换可能会导致长时间的暂停,但这么长时间听起来很不寻常。

相关内容