【问题标题】:When sockets are dereferenced, are they garbage collected当套接字被取消引用时,它们是否被垃圾收集
【发布时间】:2016-10-01 06:34:51
【问题描述】:

我正在尝试理解 SO_TIMEOUT、线程资源和垃圾回收的概念。

我在我的项目中看到了一个编码错误,其中一组套接字在没有关闭的情况下被错误地取消引用。

使用 JAVA 1.6,它们会在几分钟内关闭,但使用 JAVA 1.8,它们需要数小时才能关闭。 [仅在linux平台测试]

所以我想更深入地研究这个问题。

我创建了这个程序,在其中创建了 10 个套接字,但正如您所见,其中 9 个被取消引用,然后线程进入长时间休眠:

    import java.net.Socket;

/**
 * Created by 212477559 on 5/30/16.
 */
public class TestClientSocket {

    public static void main(String args[])
    {
        try
        {
            Socket socket;
            for(int j=0;j<10;j++)
            {
                socket =new Socket("localhost",9090);
                socket.setKeepAlive(false);
            }


            Thread.sleep(900000);

        }
       catch(Exception e)
        {
            System.out.println("throws");
            e.printStackTrace();
        }
    }
}

现在当我运行时

netstat -np TCP |找到“9090”

我仍然可以看到半小时左右后所有套接字仍处于已建立状态。所以我有以下问题:

1) 为什么这 9 个套接字没有被垃圾回收?

2) 那个线程还持有资源吗?

3) 是下面提到的关闭套接字的唯一方法:

   1. close the sockets?
   2. Thread which created those sockets must exit?
   3. SO_TIMEOUT should expire?

【问题讨论】:

  • 关闭套接字是关闭套接字的唯一正确方法。垃圾收集不是资源清理机制,超时也不是。
  • 您可能很想为此编写一个finalize() 方法,但不要这样做。当堆中的可用空间不足时,垃圾收集器运行。由于任何其他资源不足,它无法运行。
  • @zapl & james 我知道关闭套接字是正确的做法。我只是想了解我的项目问题,即它在不同的 Java 版本方面出了什么问题。所以想了解socket、线程和垃圾回收的关系
  • 你的问题只是你没有关闭你的套接字。它与线程无关,线程休眠时引用的测试代码中唯一的套接字是自socket 仍然指向它之后创建的最后一个套接字。其余的可以免费作为 GC,但您的测试程序会产生 0 垃圾,因此 GC 不需要运行。超时不会导致清理。当您调用 read 或 write 方法时,它们会给您例外。
  • 垃圾收集器没有理由在你的程序中运行。你创建了 10 个对象,然后永远沉睡。之后不会创建垃圾。在那之后也没有什么需要记忆的。对内存的需求是垃圾收集的驱动力。

标签: java multithreading sockets networking garbage-collection


【解决方案1】:

垃圾回收与这些 Sockets 仍然打开的事实无关。

简单介绍

垃圾回收是查看堆内存的过程, 识别哪些对象正在使用,哪些没有,并删除 未使用的对象。使用中的对象或引用的对象意味着 你的程序的某些部分仍然维护一个指向它的指针 目的。未使用的对象或未引用的对象不再是 被程序的任何部分引用。所以使用的内存 可以回收未引用的对象。

现在,虽然 Socket 对象使用的内存可能会被回收,但这并不意味着操作系统对 Socket 的句柄会随着 JVM 回收内存的过程而被破坏(这是两个不同的事情) .

这就是为 Socket 提供close 方法的原因。这是保证操作系统释放 Socket 句柄的唯一方法。

套接字在不再需要时应始终关闭。

使用 JAVA 1.6 但使用 JAVA 1.8,它们会在几分钟内关闭 他们需要几个小时才能关闭。

如果他们关闭,这甚至不应该成为讨论点。

【讨论】:

  • 从您的回答中,我得到了一个线索,即解除引用的对象和套接字处理程序是不同的东西。而且我也同意关闭套接字是唯一的好选择。但是我现在想了解的是,当拥有相同的平台时,那么拥有 2 个不同版本的 java 是如何改变这种行为的。如果您能给出任何可能出错的提示,那将非常有帮助
  • 在 1.6 和 1.8 之间的多次比较中,这种行为是否一致?
  • 是的,它是一致的。两个版本都完成了套接字计数。但是对于 java 1.8,与 java 1.6 中的分钟相比,它需要几个小时。我知道垃圾收集过程在 java 1.8 中发生了变化。这可能是背后的原因吗?抱歉,我只是分享提示,对相关性了解不够
  • @Haj 您可能需要在两个测试中都运行 VisualVM 之类的工具,以了解实际情况。其他任何事情都可能是纯粹的猜测。
【解决方案2】:

当本地资源由 java 对象表示时,可以在资源保持分配状态时对该对象进行垃圾回收。为了提供安全网,java对象可以重写它的finalize()方法来实现资源的释放。

我认为在具体的套接字实现中的某个地方有一个 finalize,在哪里处理它,尽管我无法快速浏览源代码。

finalize 的问题在于,它仅在对象实际垃圾回收时执行,而不是在对象变得有资格进行 GC 时执行。在您的示例代码中,您的套接字符合 GC 条件,但由于您所做的只是休眠,因此虚拟机没有实际执行 GC 的压力。

编辑:我没有强调或提及的一点,因为这对我来说很自然:only 使用套接字(或实际上任何获取操作系统级资源的任何东西)的正确方法是遵循资源提供的 close()/dispose() 协议。只有在完成后立即显式关闭资源,才能确保资源被释放。在 JVM 堆和垃圾收集经过微调的场景中,对象可能不会在很长一段时间内被收集(几天或 ,可能从不)。

【讨论】:

  • 您的回答很有见地,但我认为 Socket 类在 JDK 1.6 或 JDK 1.8 中都没有实现 finalize 方法
  • @MichaelMarkidis socket类不需要实现它,Socket只是一个普通的java对象。如果您查看源代码,您会发现它是 SocketImpl 的委托(抽象),所以大概它留给具体实现来处理最终确定。或者也许它真的根本没有实现它。
  • @MichaelMarkidis 终于找到了,finalize是在AbstractPlainSocketImpl中实现的。
  • 明白了。所以也许 GC 在 1.6 和 1.8 中被更快地调用??
  • @Haj 真的是 Durandal 和我的回答都说明了整个故事。虽然你不能只依赖垃圾收集,但你也不应该这样做。原来socket的具体实现有一个finalize方法调用close方法。
【解决方案3】:

我深入研究了这个问题并找到了@Durandal 和@Michael Markidis 暗示的有趣答案。 发生的事情是由于我的代码中的编码错误,该对象被推迟了。 现在的问题是为什么这个取消引用的对象没有在 java 1.8 中关闭套接字,而是在 java 1.6 中关闭。

我发现的是

  • 默认情况下,我系统中的 java 1.6 仅分配 12 MB 给幸存者空间
  • 默认情况下,我系统中的 java 1.8 分配了 50 MB 给幸存者空间

由于生存空间小,我的老一代开始快速填充,这导致 java 1.6 中频繁的主要垃圾收集。 但是在 java 1.8 中,主要的垃圾收集发生在几个小时后,然后我的套接字就关闭了。

所以问题被发现是因为不同 JVM 版本分配 NEW_RATIO 和 SURVIVOR_RATIO 的行为发生了变化。 当我在 java 1.8 (在 java 1.6 中分配) 中使用相同的 NEW_RATIO 和 SURVIVOR_RATIO 进行测试时,我可以看到我的套接字经常关闭。

【讨论】:

    猜你喜欢
    • 2020-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-29
    • 1970-01-01
    • 1970-01-01
    • 2011-10-05
    相关资源
    最近更新 更多