【内存】- 垃圾回收与内存泄露

游戏开发

自动内存回收

自动内存回收(GC)的核心在于判断对象是否“不可达”(即无法再被访问),从而确定其是否可以被回收。不同编程语言和运行时环境的实现细节可能不同,但核心思想是通过可达性分析来判断对象的存活状态。常见的带有自动内存回收的语言有C#,JavaScript,Lua等。

常见的内存泄露

C++

常见内存泄漏原因
  • 忘记释放内存
    1
    2
    3
    int* ptr = new int(10); // 分配内存
    // ... 使用ptr ...
    // 忘记调用 delete ptr;
  • 指针赋值错误
    1
    2
    3
    int* ptr1 = new int(10);
    int* ptr2 = ptr1; // ptr2 持有同一块内存
    delete ptr1; // 仅释放 ptr1,ptr2 仍指向已释放内存
  • 异常安全问题
    1
    2
    3
    4
    5
    void func() {
    int* ptr = new int(10);
    if (some_condition()) throw std::runtime_error("Error");
    delete ptr; // 如果抛出异常,delete 未执行
    }
如何避免
  • 使用智能指针
    1
    2
    auto ptr = std::make_unique<int>(10); // 自动释放内存
    auto shared = std::make_shared<MyClass>(); // 共享所有权
  • RAII 技术
    (没用过,需要学习下)
  • 避免混用裸指针和智能指针
    • 尽量用 std::unique_ptr 替代 new/delete
    • 避免手动管理内存,除非必要。
内存优化方案
  • 内存池
    • 对小对象(如 64 字节)预分配连续内存,减少碎片。
    • 使用 Boost.Pooltcmalloc
  • 减少动态分配
    • 优先使用栈分配或 std::array
    • 对频繁分配/释放的对象使用对象池。

C#

常见内存泄漏原因
  • 事件订阅未取消
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class A {
    public event Action MyEvent;
    }
    public class B {
    public B(A a) {
    a.MyEvent += OnMyEvent; // 未取消订阅
    }
    private void OnMyEvent() { /* ... */ }
    }
  • 静态字段或集合未清理
    1
    public static List<object> Cache = new List<object>(); // 无限增长
  • 非托管资源未释放
    1
    2
    3
    4
    public class MyResource : IDisposable {
    public void Dispose() { /* 释放非托管资源 */ }
    }
    // 忘记调用 Dispose()
  • 闭包捕获外部变量
    1
    2
    3
    4
    5
    6
    public class Worker {
    private readonly Timer _timer;
    public Worker() {
    _timer = new Timer(state => { /* 捕获 this */ }, null, 0, 1000);
    }
    }
如何避免
  • 使用 IDisposableusing
    1
    2
    3
    using (var resource = new MyResource()) {
    // 自动调用 Dispose()
    }
  • 取消事件订阅
    1
    a.MyEvent -= OnMyEvent; // 在对象销毁时解绑
  • 弱引用(WeakReference)
    • 对缓存或临时数据使用 WeakReference,避免强引用导致泄漏。
  • 避免静态集合长期持有引用
    • 对缓存设置过期策略(如 MemoryCacheAbsoluteExpiration)。

内存优化方案

  • 减少对象创建
    • 使用结构体(struct)代替类(class)存储栈数据。
    • 对频繁创建的对象使用对象池(ObjectPool<T>)。

JavaScript

常见内存泄漏原因

  • 全局变量未清理
    1
    var globalVar = new Array(1000000).fill("leak"); // 占用大量内存
  • 未清除的定时器/事件监听器
    1
    2
    setInterval(() => { /* ... */ }, 1000); // 未调用 clearInterval
    document.getElementById("btn").addEventListener("click", handler); // 未移除
  • 闭包引用
    1
    2
    3
    4
    5
    function createLeak() {
    const largeData = new Array(1000000);
    return () => { /* largeData 一直存活 */ };
    }
    const leakyFunc = createLeak();

如何避免

  • 及时清理资源
    1
    2
    3
    const timerId = setInterval(...);
    // 在组件卸载时清除
    clearInterval(timerId);
  • 避免全局变量
    • 使用模块化(ES6 Modules)或闭包封装变量。

Lua

常见内存泄漏原因

  • 未释放的表(Table)
    1
    2
    3
    local t = {}
    for i=1,1e6 do t[i] = math.random() end
    -- t 被局部变量引用,不会被 GC 回收
  • 闭包引用
    1
    2
    local t = {}
    local f = function() return #t end -- f 捕获 t,导致 t 无法回收
  • 未清理的事件绑定
    • 在游戏开发中,事件监听器未解除绑定会导致对象无法回收。

如何避免

  • 弱表(Weak Table)
    1
    2
    local weakTable = setmetatable({}, { __mode = "v" }) -- 引用值为弱引用
    weakTable.key = "value" -- key 被回收时,value 会自动删除
  • 显式释放资源
    1
    2
    3
    4
    local t = {}
    -- 使用完毕后手动置空
    t = nil
    collectgarbage() -- 强制 GC 回收
  • 避免全局变量
    • 将数据封装在局部表中,避免污染全局作用域。

内存优化方案

  • 使用轻量对象
    • table 替代类实例(Lua 本身无类,但可通过元表模拟)。
  • 对象池(Object Pool)
    • 复用对象(如游戏中的粒子效果、子弹)。
  • 标题: 【内存】- 垃圾回收与内存泄露
  • 作者:
  • 创建于 : 2025-08-06 00:33:56
  • 更新于 : 2025-08-06 01:03:04
  • 链接: https://sxl-space.tk/2025/08/06/008_GC-MemoryLeak/
  • 版权声明: 版权所有 © 宋,禁止转载。