多线程Java Web服务器内存泄漏问题排查
Hey there! Let’s walk through the most common places where your multi-threaded Java web server might be leaking memory after a socket closes. Since you haven’t shared your full code, I’ll focus on the typical culprits that cause this exact issue—these are the spots I’d check first if I were debugging your server:
This is the most frequent offender. If your request handler threads open streams (like InputStream/OutputStream), readers/writers, or even database connections, and don’t close them properly, those objects will hang around in memory. Java’s garbage collector can’t clean them up if there’s still an active reference from an unclosed resource.
- Mistake example:
// Bad: No cleanup for the input stream InputStream in = socket.getInputStream(); // Process the request data... // Oops—forgot to close 'in' when done!
Always use try-with-resources (auto-closes resources) or wrap cleanup in a finally block to avoid this.
If you’re using ThreadLocal to store request-specific data (like user context or session info), you need to explicitly call ThreadLocal.remove() when the request finishes. Most web servers reuse threads via pools—so if you don’t clear the ThreadLocal, the data sticks around in the thread’s memory forever, accumulating with every new request. Even if you think the thread dies, pool threads stay alive, so those references never get garbage collected.
If you have a static List, Map, or other collection that stores request-related objects (like the Socket itself, request payloads, or handler instances) and forget to remove entries when the socket closes, those objects will live for the entire JVM lifetime. Every request adds more data to the collection, and nothing gets cleaned up until you restart the server.
- Mistake example:
// Bad: Static map holding onto request data indefinitely private static final Map<Socket, RequestMetadata> activeRequests = new HashMap<>(); public void handleRequest(Socket socket) { RequestMetadata meta = new RequestMetadata(); activeRequests.put(socket, meta); // Process request... // Never call activeRequests.remove(socket) when done! }
If your handler starts an async operation or scheduled task (using ScheduledExecutorService) that holds a reference to the socket, request object, or related data, you must cancel that task when the socket closes. If you don’t, the task will keep those objects alive—either waiting to run or executing indefinitely—preventing garbage collection.
If you’re using plain old sockets or NIO, make sure you’re not holding onto references to Socket, SocketChannel, or SelectionKey in long-lived objects (like a custom connection manager). For NIO specifically, forgetting to call SelectionKey.cancel() or remove the key from the selector after the socket closes can leave those objects hanging in memory.
If you’ve built your own session system (instead of using a framework), double-check that you’re expiring or removing sessions when the client disconnects (socket closes). If your server still holds a reference to the session object even after the client is gone, it won’t be garbage collected, leading to gradual memory bloat.
内容的提问来源于stack exchange,提问作者Ray Smith

