You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js应用内存泄漏:配置最大512M但Heroku占用达1.5GB

Troubleshooting & Fixing Memory Leaks in Your Node.js Game Server

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 players collection 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 players creates strong references. Even if a socket is destroyed, as long as the players object 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:50:01