我试图理解SO_TIMEOUT,线程资源和垃圾收集的概念。
我在项目中看到了一个编码错误,其中套接字被错误地解除引用而没有关闭。
他们在几分钟内用JAVA 1.6关闭,但是使用JAVA 1.8,他们需要几个小时才能关闭。 [仅在linux平台上测试]
所以我想深入研究这个问题。
我创建了这个程序,我在其中创建了10个套接字但是你可以看到它们中有9个是derefernced然后线程进入长睡眠持续时间:
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?
答案 0 :(得分:3)
当本机资源由java对象表示时,可以在资源保持分配时对对象进行垃圾回收。为了提供安全网,java对象可以覆盖其finalize()方法以实现释放资源。
我假设在具体套接字实现中的某个地方有一个finalize,在这里进行处理,虽然我无法快速浏览一下这个地方。
finalize的问题是,当实际 收集了对象时,它只执行 ,而不是当对象符合GC 时。在您的示例代码中,您的套接字符合GC的条件,但由于您所做的只是休眠,因此VM无需实际执行GC。
编辑:我没有强调或提及的一点,因为对我来说很自然:仅正确使用套接字的方式(或者实际上任何获取操作系统级资源的方法)是遵循资源提供的close()/ dispose()协议。只有在完成后显式关闭资源,才能确保资源被释放。在JVM堆和垃圾收集经过精细调整的情况下,可能无法长时间收集对象(天或周,可能从不)。
答案 1 :(得分:2)
垃圾收集与这些套接字仍处于打开状态无关。
只是简单地:
垃圾收集是查看堆内存的过程, 识别正在使用哪些对象以及哪些对象未被删除 未使用的对象。使用中的对象或引用的对象是指 程序的某些部分仍然保持指向它的指针 宾语。不再使用未使用的对象或未引用的对象 由程序的任何部分引用。所以内存使用的是 可以回收未引用的对象。
现在,虽然可以回收Socket对象使用的内存,但这并不意味着Sockets的操作系统句柄会随着JVM回收内存的过程而被破坏(这两个是分开的)的东西)。
这就是为套接字提供close方法的原因。这是保证操作系统释放套接字句柄的唯一方法。
套接字应始终在不再需要时关闭。
他们在几分钟内用JAVA 1.6关闭,但使用JAVA 1.8 他们需要几个小时才能完成。
如果它们关闭,这甚至不应成为讨论点。
答案 2 :(得分:0)
我深入研究了这个问题,找到了一个有趣的答案,@ Durandal和@Michael Markidis也暗示了这个答案。 发生的事情是由于我的代码中的编码错误,该对象是受到尊重的。 现在的问题是为什么这个解除引用的对象没有关闭java 1.8中的套接字,而是关闭java 1.6。
我发现的是
由于存储空间很小,我的旧版本开始快速填充,导致java 1.6中频繁的主要垃圾收集。 但是在java 1.8中,主要的垃圾收集发生在数小时之后,之后我的插座被关闭了。
因此,由于diff JVM版本分配NEW_RATIO和SURVIVOR_RATIO的行为发生了变化,因此发现了该问题。 当我在java 1.8 (在java 1.6中分配)中使用相同的NEW_RATIO和SURVIVOR_RATIO进行测试时,我可以看到我的套接字经常关闭。