zoukankan      html  css  js  c++  java
  • 初步探究java中程序退出、GC垃圾回收时,socket tcp连接的行为

    初步探究java中程序退出、GC垃圾回收时,socket tcp连接的行为

    今天在项目开发中需要用到socket tcp连接相关(作为tcp客户端),在思考中发觉需要理清socket主动、被动关闭时发生了什么,所以做了一番实验,验证socket tcp连接在调用close、被GC回收、程序运行完毕退出、程序进程被杀掉时,tcp会产生什么行为。得出了一些结论,记录于此同时分享给大家。

    先写出得到的结论:

    1. java程序运行完毕退出和被杀进程时,socket tcp连接会被关闭。而且是通过发送RST方式关闭tcp,不是四次挥手方式关闭tcp,不会进入TIME_WAIT状态。(一般在关闭异常连接时,使用发出RST复位标志的方式)
    2. 在socket对象被GC回收时,socket的close()方法会被调用,此时将使用四次挥手方式关闭主动tcp连接,随后进入一段时间的TIME_WAIT状态。主动调用socket的close()方法效果相同。
    3. 在使用new Socket(host,port)创建socket对象后,便会建立起tcp连接。

     

    以上结论是个人测试得到的,如果有出入欢迎指正。

    PS:下方是new Socket(host,port)构造方法的代码,结合上面结论第3条,可见在构造方法中第40行进行了一些业务逻辑。联想到前段时间广泛传播的《阿里巴巴JAVA开发手册.pdf》文档,其中有这样一条:

    11.【强制】构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。

    可见这个规则也不是处处适用的(至少此处jdk中Socket的构造方法就与这条规则相悖),在使用时考虑到团队协作、符合习惯等时考虑遵守这条规则。

     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     }

    验证时,使用的工具:

    • windows cmd命令行,“netstat -ano|findstr 9911”命令(9911是我测试时连接的对方服务器端口),用以查看tcp连接情况,包括tcp是否存在,以及处于TIME_WAIT、ESTABLISHED或者别的状态;
    • wireshark,抓包详细查看tcp中传输的数据包;
    • JProfiler,java性能分析工具,我在使用时和IDEA做了集成,这里用它的主要目的是手动触发GC,以及验证是否进行了GC。

    具体验证过程:

    使用以下测试代码

     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,不杀进程:

    1. 在14行之前,没有创建tcp连接;
    2. 14行创建了Socket对象之后,tcp连接就已经建立了,此时socket.isConnected()为true,socket.isClosed()为false;
    3. 19行执行时,没有发现tcp中有任何数据传输,说明获取输出流是不需要网络层的动作的;
    4. 19行之后,程序退出之前,tcp一直是ESTABLISHED状态(前提是这期间没有发生过GC);
    5. 程序退出后,tcp连接马上消失(没有进入TIME_WAIT状态),通过抓包查看是主动发出了RST(发出RST reset复位标志,一般是关闭异常tcp连接的方法)。

     

    以下wireshark抓包的截图中序号为1-4的包,是上述过程产生的。

    测试方案2,tcp连接建立之后,程序退出前,杀掉进程:

    和方案1中的效果相同,上图中5-8号包是方案2产生的。杀掉进程时,会主动发出RST关闭tcp连接。

    测试方案3,tcp连接建立后,GC回收socket对象:

    触发GC的方法,是使用JProfiler的Run GC按钮,触发GC,如下图,内存变化说明确实进行了GC。

    具体测试的设计,socket对象是main2方法中的局部变量,在main2执行结束后便可以被回收。

    在测试中,第2、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影响的初步探究,如有错误、疏漏还请斧正。

    转载请注明出处:http://www.cnblogs.com/zhangdong92/p/7056539.html

  • 相关阅读:
    4.8 C++ typeid操作符
    4.7 C++ dynamic_cast操作符
    tomcat中class和jar的加载顺序(转)
    java Files类和Paths类的用法 (转)
    搭建DUBBO项目解决DUBBO.XML标签报错的问题(转载)
    Maven异常:Could not find artifact
    在docker宿主机上查找指定容器内运行的所有进程的PID
    jmap、jstack、jps无法连接jvm解决办法
    linux中如何查看某一进程的启动时间
    Eureka与ZooKeeper 的比较(转)
  • 原文地址:https://www.cnblogs.com/zhangdong92/p/7056539.html
Copyright © 2011-2022 走看看