【内存】- 垃圾回收与内存泄露
自动内存回收
自动内存回收(GC)的核心在于判断对象是否“不可达”(即无法再被访问),从而确定其是否可以被回收。不同编程语言和运行时环境的实现细节可能不同,但核心思想是通过可达性分析来判断对象的存活状态。常见的带有自动内存回收的语言有C#,JavaScript,Lua等。
常见的内存泄露
C++
常见内存泄漏原因
- 忘记释放内存:
1
2
3int* ptr = new int(10); // 分配内存
// ... 使用ptr ...
// 忘记调用 delete ptr; - 指针赋值错误:
1
2
3int* ptr1 = new int(10);
int* ptr2 = ptr1; // ptr2 持有同一块内存
delete ptr1; // 仅释放 ptr1,ptr2 仍指向已释放内存 - 异常安全问题:
1
2
3
4
5void func() {
int* ptr = new int(10);
if (some_condition()) throw std::runtime_error("Error");
delete ptr; // 如果抛出异常,delete 未执行
}
如何避免
- 使用智能指针:
1
2auto ptr = std::make_unique<int>(10); // 自动释放内存
auto shared = std::make_shared<MyClass>(); // 共享所有权 - RAII 技术:
(没用过,需要学习下) - 避免混用裸指针和智能指针:
- 尽量用
std::unique_ptr替代new/delete。 - 避免手动管理内存,除非必要。
- 尽量用
内存优化方案
- 内存池:
- 对小对象(如 64 字节)预分配连续内存,减少碎片。
- 使用
Boost.Pool或tcmalloc。
- 减少动态分配:
- 优先使用栈分配或
std::array。 - 对频繁分配/释放的对象使用对象池。
- 优先使用栈分配或
C#
常见内存泄漏原因
- 事件订阅未取消:
1
2
3
4
5
6
7
8
9public 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
4public class MyResource : IDisposable {
public void Dispose() { /* 释放非托管资源 */ }
}
// 忘记调用 Dispose() - 闭包捕获外部变量:
1
2
3
4
5
6public class Worker {
private readonly Timer _timer;
public Worker() {
_timer = new Timer(state => { /* 捕获 this */ }, null, 0, 1000);
}
}
如何避免
- 使用
IDisposable和using:1
2
3using (var resource = new MyResource()) {
// 自动调用 Dispose()
} - 取消事件订阅:
1
a.MyEvent -= OnMyEvent; // 在对象销毁时解绑
- 弱引用(WeakReference):
- 对缓存或临时数据使用
WeakReference,避免强引用导致泄漏。
- 对缓存或临时数据使用
- 避免静态集合长期持有引用:
- 对缓存设置过期策略(如
MemoryCache的AbsoluteExpiration)。
- 对缓存设置过期策略(如
内存优化方案
- 减少对象创建:
- 使用结构体(
struct)代替类(class)存储栈数据。 - 对频繁创建的对象使用对象池(
ObjectPool<T>)。
- 使用结构体(
JavaScript
常见内存泄漏原因
- 全局变量未清理:
1
var globalVar = new Array(1000000).fill("leak"); // 占用大量内存
- 未清除的定时器/事件监听器:
1
2setInterval(() => { /* ... */ }, 1000); // 未调用 clearInterval
document.getElementById("btn").addEventListener("click", handler); // 未移除 - 闭包引用:
1
2
3
4
5function createLeak() {
const largeData = new Array(1000000);
return () => { /* largeData 一直存活 */ };
}
const leakyFunc = createLeak();
如何避免
- 及时清理资源:
1
2
3const timerId = setInterval(...);
// 在组件卸载时清除
clearInterval(timerId); - 避免全局变量:
- 使用模块化(ES6 Modules)或闭包封装变量。
Lua
常见内存泄漏原因
- 未释放的表(Table):
1
2
3local t = {}
for i=1,1e6 do t[i] = math.random() end
-- t 被局部变量引用,不会被 GC 回收 - 闭包引用:
1
2local t = {}
local f = function() return #t end -- f 捕获 t,导致 t 无法回收 - 未清理的事件绑定:
- 在游戏开发中,事件监听器未解除绑定会导致对象无法回收。
如何避免
- 弱表(Weak Table):
1
2local weakTable = setmetatable({}, { __mode = "v" }) -- 引用值为弱引用
weakTable.key = "value" -- key 被回收时,value 会自动删除 - 显式释放资源:
1
2
3
4local 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/
- 版权声明: 版权所有 © 宋,禁止转载。