Java中基於時間的32位溢出?還是 SLES11?
這是 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 天以上)內的**一秒內退出。**在每台機器上,兩個執行緒在 JVM 退出之前產生堆棧跟踪(Tomcat 自己
System.exit(1)
在獲取時呼叫,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)
循環,Socket
通過呼叫得到 aServerSocket.accept()
並且accept()
呼叫炸彈出來。有什麼想法為什麼會發生這種情況,以及我可以嘗試查看/尋找什麼來弄清楚將來如何防止它?
請注意,當Tomcat執行 2^32 毫秒時,系統在 Tomcat 啟動時已經啟動。當然,這並不排除在 Tomcat 開始參與時創建的一些流程變數。
我最近也看到了這個問題,它似乎與 Java 6 和 7 之間的 32 位 Oracle JVM 中所做的更改無關。在 Linux 上,使用 strace 執行 32 位 Java 7 VM 會顯示以下系統呼叫在未設置 SO_TIMEOUT 的情況下呼叫 ServerSocket.accept() 時:
32369 poll([{fd=5, events=POLLIN|POLLERR}], 1, 4294967295 <unfinished ...>
對 poll() 的呼叫傳遞了 2^32 毫秒 (4294967295) 的超時值,而不是表示無限超時的預期負值。這最終會導致 ServerSocket.accept() 拋出 SocketTimeoutException,從而導致 Tomcat 的引導程式碼執行伺服器關閉。Tomcat 的那個特定部分從不期望 ServerSocket.accept 拋出 SocketTimeoutException。
如果可以操縱對 poll() 的呼叫,則更容易重現此問題,這樣您就不必等待 2^32 毫秒。這可以通過覆蓋 poll 系統呼叫在 Linux 中完成。執行此操作的一種方法是使用 LD_PRELOAD 指令來載入 poll 的覆蓋版本。可以在https://github.com/vi/timeskew找到一些顯示此想法的範常式式碼。不幸的是,它不會覆蓋 poll,但可以很容易地擴展來這樣做。