如果一个ServerSocket被垃圾回收了,那么它所关联的端口绑定会发生什么?

3
我想创建一个应用程序,能够检测到其他的自身实例并阻止它们运行。为了做到这一点,我考虑开启一个新的ServerSocket,在某个特定于应用程序的端口上,并依靠如果我尝试绑定同一端口超过一次时会抛出的异常来“检测”和关闭重复的应用程序实例。我知道我可以像写入当前工作目录中的文件并“检测”它那样完成相同行为,但我真的不想这样做(如果应用程序崩溃并且无法删除该文件会发生什么?),所以我选择了ServerSocket路线。
假设我有以下代码:
public class MyClass{
    public static void main(String[] args) throws IOException{
        new ServerSocket(1234);

        new Thread(){
            //This is a non-daemon thread. Assume its run() method never returns.
        }.start();
    }
}

问题

除了创建ServerSocket之外,我的应用程序从未真正需要再次使用它,因为其存在仅允许我检测另一个实例的启动。 因此,保存对该ServerSocket的引用将导致编译警告(未使用的引用)。 我是个整洁狂人,所以如果可以避免,我宁愿不保存引用。 我的问题是,在所有非守护线程退出之前,是否会在垃圾回收之前回收此ServerSocket实例(假设应用程序不会失败或以其他方式退出),如果是,那么它关联的端口会因此变得未绑定吗?


这听起来像是一个可怕的想法。如果你真的想保持类的单个实例,我建议你考虑使用 D-bus http://www.freedesktop.org/wiki/Software/dbus 。我相信 Windows 也有类似的东西。 - Falmarri
1
@Falmarri,您的观点是错误的。这个想法不是一个单例类,而是一个单例程序。我想确保该特定的main()方法在每台机器上都不会被执行超过一次,而不是每个JVM。 - CodeBlind
1
@Falmarri 这是标准技术。两行代码而不是外部产品?毫无疑问。 - user207421
@BenLawry 另一方面,我认为一个单行的静态变量声明不会让你崩溃。 - user207421
@EJP,你说得对,确实不是这样的。我只是希望ServerSocket和垃圾回收的行为能够更好地定义。不过话说回来,我想任何编写Java代码的人都希望Java垃圾回收能够更好地定义一些 ;) - CodeBlind
2个回答

4
我很困惑:你是一个“洁癖患者”,但在正常退出的情况下,你宁愿把套接字关闭的控制权留给垃圾回收器,而不是让应用程序控制关闭过程?(我认为是这种情况,因为要实现这样的机制,你需要持有引用,从而消除了你所感知的问题。)
就其价值而言,如果你不保留引用,我认为实际上不会出现问题:
内部可能会在绑定套接字时保留引用
ServerSocket的close()方法可能会在垃圾回收时被调用。它在AbstractPlainSocketImpl的finalize()方法中被调用,因此原则上会被调用,但前提是没有保证任何finalize方法实际上会被调用。
如果应用程序异常终止,则不能保证立即解除套接字的绑定,尽管在最近的操作系统上这很可能是(最好测试一下)。
然而,我真的建议为应用程序正常关闭的情况编写“清理”关闭机制,为此你将需要保留对套接字的引用。因此,我认为如果你只是使用合理的编程实践,就不必为自己发明问题。

我不确定你是怎么理解这个问题的,所以让我澄清一下 - 我宁愿将这个特定的套接字关闭留给 JVM 和底层操作系统处理,而不是垃圾回收器。我只是好奇如果我按照这种方式编写程序,垃圾回收器会实际做什么。用侮辱性的言语开始回答并假设我没有使用“合理的编程实践”,这是在回避问题,也没有太大的帮助。 - CodeBlind
“虽然在最近的操作系统中,套接字可能会立即解除绑定(最好测试一下),但不能保证套接字会立即解除绑定。” 在我使用过的25年中,除了Netware之外,所有情况下操作系统都提供了这种保证。 - user207421
@EJP 恭喜您使用的操作系统提供了这个保证,并且它始终没有失败。我曾经使用过其中一个条件不成立的操作系统... :-) [包括至少一个名为“Windows”的操作系统版本。] - Neil Coffey
@NeilCoffey,你忽略了Netware的部分:它没有释放某些资源,这就是我提到它的原因。自从3.0版本以来,我使用过的所有Windows版本都应该在进程退出时释放所有进程资源。如果它们没有这样做,那么这是一个错误,而不是需要由Java保证的东西。 - user207421
更重要的是 - 看起来 ServerSocket 直接继承了 Object,但没有覆盖 finalize()。似乎在垃圾回收之前没有明确关闭的 ServerSocket 实例实际上不会放弃其本地资源,直到它们的父 JVM 退出。 - CodeBlind
@BenLawry 但是它也有文档说明依赖于创建SocketImplsSocketFactory,这些SocketImpls可能会有finalizers而你并不知道。 - user207421

1

好的,让我试着从之前的误解中挽回自己。您提到的文件锁和套接字技术被广泛使用,但还有另一种方法——拥有一个观察者,通过注册和注销方法来保持当前实例的程序。

如果新实例尝试在另一个实例运行时注册,注册过程将失败,您的应用程序可以优雅地关闭。

然后,您的应用程序可以实现一个Observable接口,其中包含一个beObserved方法,以便您的观察者知道您的应用程序仍然存活。因此,如果您的应用程序崩溃,定期检查将失败,并自动注销崩溃的应用程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接