初步探究java中程序退出、GC垃圾回收時,socket tcp鏈接的行爲html
今天在項目開發中須要用到socket tcp鏈接相關(做爲tcp客戶端),在思考中發覺須要理清socket主動、被動關閉時發生了什麼,因此作了一番實驗,驗證socket tcp鏈接在調用close、被GC回收、程序運行完畢退出、程序進程被殺掉時,tcp會產生什麼行爲。得出了一些結論,記錄於此同時分享給你們。java
先寫出獲得的結論:windows
以上結論是我的測試獲得的,若是有出入歡迎指正。服務器
PS:下方是new Socket(host,port)構造方法的代碼,結合上面結論第3條,可見在構造方法中第40行進行了一些業務邏輯。聯想到前段時間普遍傳播的《阿里巴巴JAVA開發手冊.pdf》文檔,其中有這樣一條:網絡
11.【強制】構造方法裏面禁止加入任何業務邏輯,若是有初始化邏輯,請放在 init 方法中。 app
可見這個規則也不是到處適用的(至少此處jdk中Socket的構造方法就與這條規則相悖),在使用時考慮到團隊協做、符合習慣等時考慮遵照這條規則。jvm
1 /** 2 * Creates a stream socket and connects it to the specified port 3 * number on the named host. 4 * <p> 5 * If the specified host is {@code null} it is the equivalent of 6 * specifying the address as 7 * {@link java.net.InetAddress#getByName InetAddress.getByName}{@code (null)}. 8 * In other words, it is equivalent to specifying an address of the 9 * loopback interface. </p> 10 * <p> 11 * If the application has specified a server socket factory, that 12 * factory's {@code createSocketImpl} method is called to create 13 * the actual socket implementation. Otherwise a "plain" socket is created. 14 * <p> 15 * If there is a security manager, its 16 * {@code checkConnect} method is called 17 * with the host address and {@code port} 18 * as its arguments. This could result in a SecurityException. 19 * 20 * @param host the host name, or {@code null} for the loopback address. 21 * @param port the port number. 22 * 23 * @exception UnknownHostException if the IP address of 24 * the host could not be determined. 25 * 26 * @exception IOException if an I/O error occurs when creating the socket. 27 * @exception SecurityException if a security manager exists and its 28 * {@code checkConnect} method doesn't allow the operation. 29 * @exception IllegalArgumentException if the port parameter is outside 30 * the specified range of valid port values, which is between 31 * 0 and 65535, inclusive. 32 * @see java.net.Socket#setSocketImplFactory(java.net.SocketImplFactory) 33 * @see java.net.SocketImpl 34 * @see java.net.SocketImplFactory#createSocketImpl() 35 * @see SecurityManager#checkConnect 36 */ 37 public Socket(String host, int port) 38 throws UnknownHostException, IOException 39 { 40 this(host != null ? new InetSocketAddress(host, port) : 41 new InetSocketAddress(InetAddress.getByName(null), port), 42 (SocketAddress) null, true); 43 }
驗證時,使用的工具:socket
具體驗證過程:tcp
使用如下測試代碼ide
1 public static void main(String[] args) throws Exception { 2 main2(); 3 System.out.println("調用結束"); 4 Thread.sleep(15000); 5 System.out.println("退出程序"); 6 7 } 8 9 public static void main2() throws Exception { 10 11 System.out.println("啓動"); 12 Thread.sleep(5000); 13 System.out.println("建立socket對象"); 14 Socket socket = new Socket("123.56.113.123", 9911); 15 System.out.println("socket.isConnected() = " + socket.isConnected()); 16 System.out.println("socket.isClosed() = " + socket.isClosed()); 17 Thread.sleep(5000); 18 System.out.println("獲取輸出流"); 19 OutputStream outputStream = socket.getOutputStream(); 20 System.out.println("socket.isConnected() = " + socket.isConnected()); 21 System.out.println("socket.isClosed() = " + socket.isClosed()); 22 System.out.println("即將退出"); 23 // socket.shutdownOutput(); 24 Thread.sleep(5000); 25 System.out.println("退出"); 26 27 }
爲了方便在執行不一樣的操做之間,進行手動的tcp鏈接狀況查詢,代碼中加了一些sleep()。
測試方案1,程序正常執行,不手動GC,不殺進程:
如下wireshark抓包的截圖中序號爲1-4的包,是上述過程產生的。
測試方案2,tcp鏈接創建以後,程序退出前,殺掉進程:
和方案1中的效果相同,上圖中5-8號包是方案2產生的。殺掉進程時,會主動發出RST關閉tcp鏈接。
測試方案3,tcp鏈接創建後,GC回收socket對象:
觸發GC的方法,是使用JProfiler的Run GC按鈕,觸發GC,以下圖,內存變化說明確實進行了GC。
具體測試的設計,socket對象是main2方法中的局部變量,在main2執行結束後即可以被回收。
在測試中,第二、3行代碼執行完以後,我點擊Run GC按鈕,預計socket對象會被回收。
此時用「netstat -ano|findstr 9911」命令查看,以下圖,tcp已經由ESTABLISHED狀態變爲TIME_WAIT狀態。
直到程序結束退出,仍然是TIME_WAIT狀態。(正常狀況下,TIME_WAIT狀態會持續MSL時間的2倍,即報文最大生存時間的2倍,Windows下默認爲4分鐘)
實際上,在GC時會調用socket的close()方法,致使主動關閉tcp,進入TIME_WAIT狀態。參考
java - If the jvm gc an unclosed socket instance what would happen to the underlying tcp connection? - Stack Overflow
https://stackoverflow.com/questions/25543149/if-the-jvm-gc-an-unclosed-socket-instance-what-would-happen-to-the-underlying-tc
在Socket的成員變量中有SocketImpl類型的變量impl,impl實例化的實現類都是直接或間接繼承java.net.AbstractPlainSocketImpl的,AbstractPlainSocketImpl中有以下方法:
1 /** 2 * Cleans up if the user forgets to close it. 3 */ 4 protected void finalize() throws IOException { 5 close(); 6 }
在GC時,impl的finalize方法會被調用,這時候就至關於調用了socket.close(),因此tcp被正常地主動關閉(socket.close()內的代碼也是調用impl.close(),只是額外加了線程同步控制代碼)。
測試方案4,主動調用socket.close()關閉tcp鏈接:
即將第23行代碼取消註釋,替換爲socket.close(),或socket.shutdownOutput() ,測試結果均和方案3的一致,close或shutdownOutput以後tcp關閉,端口進入TIME_WAIT狀態。
以上是本次測試驗證的過程,對java程序退出、GC回收socket對象、被殺進程、主動close時對tcp影響的初步探究,若有錯誤、疏漏還請斧正。