Node.js应用内存泄漏:配置最大512M但Heroku占用达1.5GB
Hey there, let's break down why your server's memory is spiking to 1.5GB (way over your 512MB limit) even with only ~20 concurrent users, and how to fix it. Based on your code snippet and description, the core issue is almost certainly unreleased references to player objects tied to session IDs/sockets.
First, Let's Diagnose the Root Causes
Here are the most likely culprits:
- Missing disconnect cleanup: You're storing player objects by session ID, but failing to remove those objects when a user's socket disconnects (either intentionally or due to a dropped connection). These objects hang around in memory forever because the global
playerscollection still references them, so the garbage collector (GC) can't reclaim them. - Unremoved event listeners: If you've bound custom event handlers to sockets (like
move,chat, etc.), those handlers create closures that hold references to player objects. If you don't unbind these listeners when a socket disconnects, the GC can't collect either the listener or the player object. - Strong references in global storage: Using a plain object/array for
playerscreates strong references. Even if a socket is destroyed, as long as theplayersobject has an entry for that session ID, the player object stays in memory. - Stale session data: If users leave without triggering a proper disconnect (e.g., browser crash, network outage), their session IDs might still be tied to player objects that never get cleaned up.
Step-by-Step Fixes & Improvements
1. Enforce Cleanup on Socket Disconnect
This is the most critical fix. Make sure you explicitly remove player references when a socket disconnects. Add this to your socket connection logic:
io.on('connection', (socket) => { // Initialize player object (replace with your actual logic) players[socket.sessionID] = { id: socket.sessionID, // ... other player data }; // Handle disconnect to clean up references socket.on('disconnect', () => { // Delete the player from your global collection delete players[socket.sessionID]; // Unbind any custom event listeners you added to the socket // Example: socket.off('playerMove', handlePlayerMove); // Optional: For debugging, log the cleanup to verify console.log(`Cleaned up player ${socket.sessionID}`); }); });
2. Use WeakMap for Automatic GC
Instead of a plain object for players, switch to a WeakMap. WeakMaps hold weak references to their keys—when the key (in this case, the socket object) is no longer referenced elsewhere, the GC automatically removes the entire entry from the WeakMap. This eliminates the need to manually delete entries in most cases:
// Replace your const players... line with this const players = new WeakMap(); io.on('connection', (socket) => { const player = { id: socket.sessionID, // ... player data }; // Store the player using the socket as the key players.set(socket, player); socket.on('disconnect', () => { // Optional: Explicit delete (WeakMap will auto-clean, but this is safe) players.delete(socket); }); });
3. Clean Up Stale Sessions
For users who drop connections without triggering disconnect, add a timeout to remove inactive players:
io.on('connection', (socket) => { const player = { id: socket.sessionID, lastActive: Date.now(), // ... other data }; players[socket.sessionID] = player; // Reset last active time on any user action socket.on('playerAction', () => { player.lastActive = Date.now(); }); // Check for inactive players every 5 minutes const cleanupInterval = setInterval(() => { const now = Date.now(); for (const [sessionID, player] of Object.entries(players)) { // Remove players inactive for 10 minutes if (now - player.lastActive > 600000) { delete players[sessionID]; // If needed, notify the client or clean up related resources } } }, 300000); socket.on('disconnect', () => { clearInterval(cleanupInterval); delete players[socket.sessionID]; }); });
4. Debug with Node.js Memory Tools
To confirm you've fixed the leak, use Node.js's built-in inspection tools:
- Start your server with
node --inspect your-server-file.js - Open Chrome, go to
chrome://inspect, and connect to your server - Go to the Memory tab, take a heap snapshot, then wait 10-15 minutes and take another. Compare the two snapshots—you should see that player objects are being reclaimed instead of accumulating.
5. Upgrade Dependencies
Old versions of socket.io had known memory leak issues. Make sure you're on the latest stable version:
npm update socket.io
Final Checks
- Verify that no other parts of your code are holding references to player objects (e.g., in event emitters, caches, or global variables).
- On Heroku, enable memory metrics to track if your memory usage drops after implementing these fixes.
内容的提问来源于stack exchange,提问作者Tom

