这是 Tomcat 6.0.18、Java 1.7.0_03(32 位)和 SLES11 SP2(64 位)。至于内核信息:
$ uname -a
Linux server-1 3.0.13-0.27-default #1 SMP Wed Feb 15 13:33:49 UTC 2012 (d73692b) x86_64 x86_64 x86_64 GNU/Linux
我们在三台服务器上进行负载和寿命测试。在所有三台独立的机器上,我们让 Tomcat在每台 Tomcat 启动后 2^32 毫秒(49 天以上)的一秒内退出。System.exit(1)
在每台机器上,两个线程在 JVM 退出之前产生堆栈跟踪(Tomcat 本身在获取时调用,SocketTimeoutException
这就是 JVM 退出的原因)。
一个线程是(默认情况下)在端口 8005 上侦听关闭命令的线程(通过查看 Tomcat 源代码验证了这一点):
Jun 22, 2012 9:10:15 AM org.apache.catalina.core.StandardServer await
SEVERE: StandardServer.await: accept:
java.net.SocketTimeoutException: Accept timed out
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(Unknown Source)
at java.net.ServerSocket.implAccept(Unknown Source)
at java.net.ServerSocket.accept(Unknown Source)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:389)
at org.apache.catalina.startup.Catalina.await(Catalina.java:642)
at org.apache.catalina.startup.Catalina.start(Catalina.java:602)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:288)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:413)
另一个线程是(我们相信,虽然我们没有检查 Tomcat 源来验证)处理传入端口 8080 连接的线程:
Jun 22, 2012 9:10:15 AM org.apache.jk.common.ChannelSocket acceptConnections
WARNING: Exception executing accept
java.net.SocketTimeoutException: Accept timed out
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(Unknown Source)
at java.net.ServerSocket.implAccept(Unknown Source)
at java.net.ServerSocket.accept(Unknown Source)
at org.apache.jk.common.ChannelSocket.accept(ChannelSocket.java:307)
at org.apache.jk.common.ChannelSocket.acceptConnections(ChannelSocket.java:661)
at org.apache.jk.common.ChannelSocket$SocketAcceptor.runIt(ChannelSocket.java:872)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690)
at java.lang.Thread.run(Unknown Source)
Tomcat 没有做任何疯狂的事情。在第一种情况下,它只是一个通过调用while (true)
获得 a 的循环,然后调用炸弹。Socket
ServerSocket.accept()
accept()
任何想法为什么会发生这种情况以及我可以尝试查看/寻找什么来弄清楚将来如何预防它?
请注意,虽然Tomcat运行了 2^32 毫秒,但在 Tomcat 启动时系统已经启动。当然,这并不排除在 Tomcat 开始涉及时创建的一些流程变量。
我最近也看到了这个问题,它似乎与 Java 6 和 7 之间的 32 位 Oracle JVM 中所做的更改无关。在 Linux 上,使用 strace 运行 32 位 Java 7 VM 显示以下系统调用在没有设置 SO_TIMEOUT 的情况下调用 ServerSocket.accept() 时:
对 poll() 的调用传递了 2^32 毫秒 (4294967295) 的超时值,而不是指示无限超时的预期负值。这最终会导致 ServerSocket.accept() 抛出 SocketTimeoutException,从而导致 Tomcat 的引导代码执行服务器关闭。那个特定的 Tomcat 片段从不期望 ServerSocket.accept 抛出 SocketTimeoutException。
如果可以操纵对 poll() 的调用以便您不必等待 2^32 毫秒,则重现此问题会更容易。这可以在 Linux 中通过覆盖 poll 系统调用来完成。执行此操作的一种方法涉及使用 LD_PRELOAD 指令加载轮询的覆盖版本。可以在https://github.com/vi/timeskew找到展示此想法的一些示例代码。不幸的是,它不会覆盖轮询,但可以很容易地扩展到这样做。
我在 64 位 Debian Linux 上使用 Tomcat 7.0.35 和 Java 1.7.0_10(32 位)时遇到了同样的问题。
在我的案例中,更新和使用 64 位 JDK 解决了这个问题