In a multithreaded Java application, I just tracked down a strange-looking bug, realizing that what seemed to be happening was this:
- one of my objects was storing a reference to an instance of
ServerSocket
- on startup, one thread would, in its main loop in
run()
, callaccept()
on the socket - while the socket was still waiting for a connection, another thread would try to restart the component
- under some conditions, the restart process missed the cleanup sequence before it reached the initialization sequence
- as a result, the reference to the socket was overwritten with a new instance, which then wasn't able to
bind()
anymore - the socket which was blocking inside the
accept()
wasn't accessible anymore, leaving a complete shutdown and restart of the application as the only way to get rid of it.
Which leaves me wondering: a) Does the blocking call prevent, or interfere with, GC in any way? b) If the ServerSocket does get GCed, will that make the socket available again?
In general, what are good practices I can follow to avoid this type of bug? 开发者_开发问答For instance, I learned two lessons here:
- All lifecycle logic (i. e. component level, init-start-stop-cleanup cycles) must be synchronized. Rather obvious, I guess, but I didn't take it seriously enough.
- Lifecycle logic should be as simple as possible to avoid my problem of non-obvious code paths that skip cleanup or initialization steps.
You should always close()
ServerSocket if you want a predictable cleanup.
The fact that all the references have been dropped only means that it is available for GC, not that it ever will be GC'ed. With available process memory in megabytes, there is a very small chance that a single object will be garbage collected in short period.
Usually you call close
from a maintenance thread. In many server apps it would be called from the shutdown hook.
To answer the question in the title: nothing will happen to the ServerSocket. There's still a reference to your ServerSocket from the call stack of the thread blocking on the accept() call; this reference will keep the ServerSocket from being eligible for garbage colelction.
Since you brought up the topic of life cycle methods, let me share something I had done when I wanted to create adapters for different socket oriented protocols.
I made a generic component called SocketServer which listens on a port and accepts connections. Once a connection is established, it passes the Socket object to a class implementing SocketHandler in a separate thread.
SocketHandler has methods like onSocket(Socket socket); init(); destroy();
SocketServer has methods like startServer(); stopServer();
The socket server's properties such as port and SocketHandler instance can be easily configured using Spring.
精彩评论