1. 内存泄漏基础概念
大家好,我是你们这门课的主讲。在内存管理这个领域摸爬滚打了十几年,踩过的坑比写过的代码还多。今天咱们先聊聊最基础、也最容易被忽视的问题——内存泄漏到底是什么。
1.1 什么是内存泄漏
说白了,内存泄漏就是程序向系统申请了一块内存,用完之后忘了还回去。系统以为你还在用,别人也用不了。久而久之,可用内存越来越少,程序就越来越慢,最后崩了。
我习惯用一个比喻来解释:你去图书馆借书,借了不还。图书馆的书架就那么多,你占着不走,别人就借不到。最后整个图书馆的书都被你一个人占着,别人没法用了。这就是内存泄漏。
在C/C++里,你调用了malloc()或new,但没有对应的free()或delete。在Python里,你创建了对象,但引用计数没归零,垃圾回收器也收不走。这些都是内存泄漏。
核心定义:内存泄漏是指程序在运行过程中,动态分配的内存空间在使用完毕后没有被释放,导致这部分内存无法被重新利用。
1.2 内存泄漏的危害
你可能觉得,泄漏一点点内存没什么大不了的。嗯,我以前也这么想。直到有一次,我负责的一个服务端程序跑了三天三夜,突然就挂了。查了半天,发现是一个循环里每次new了一个对象,但忘了delete。三天下来,泄漏了几百兆内存,系统直接OOM杀掉了进程。
内存泄漏的危害,我总结了几点:
- 性能下降:可用内存变少,系统频繁进行页面交换,程序越来越慢
- 系统崩溃:内存耗尽,进程被操作系统强制终止
- 资源浪费:泄漏的内存无法被其他程序使用,降低整体系统效率
- 难以排查:泄漏往往在长时间运行后才暴露,定位问题非常痛苦
注意:内存泄漏是慢性病。它不会立刻杀死你的程序,但会在你最意想不到的时候给你致命一击。我曾经见过一个嵌入式设备,因为内存泄漏,运行两个月后突然死机——这种问题在产线上排查,成本极高。
1.3 内存泄漏与内存溢出的区别
这两个概念经常被混淆。我面试的时候,十个人里有八个说不清楚。咱们今天一次讲明白。
| 对比项 | 内存泄漏 | 内存溢出 |
|---|---|---|
| 本质 | 内存没释放,占着茅坑不拉屎 | 内存不够用,想拉屎没坑位 |
| 表现 | 可用内存逐渐减少 | 直接报错,分配失败 |
| 原因 | 忘记释放、引用未清除 | 申请过大、内存碎片、泄漏累积 |
| 后果 | 程序越来越慢,最终崩溃 | 立即崩溃或异常 |
| 排查难度 | 难,需要长时间观察 | 相对容易,有明确报错 |
举个例子你就明白了:
内存泄漏:你每次点按钮都创建一个对话框,但关闭时没销毁。点100次,内存被占用了100份。程序越来越卡,最后崩了。
内存溢出:你试图一次性加载一个10GB的文件到内存里,但你的机器只有4GB内存。程序直接报错:std::bad_alloc 或者 OutOfMemoryError。
我的经验:内存泄漏往往是内存溢出的根本原因。很多OOM问题,追根溯源都是某个地方泄漏了。所以,治理内存泄漏,就是在预防内存溢出。
1.4 知识体系总览
为了让你对整个章节有个直观认识,我画了一张图。这张图把内存泄漏的核心概念、危害、以及和内存溢出的关系都串起来了。
这张图把咱们这一章的核心内容都串起来了。左边是定义,中间是危害,右边是和内存溢出的对比。你多看几遍,脑子里就有个框架了。
1.5 一个简单的代码示例
光说不练假把式。咱们看一段代码,直观感受一下内存泄漏长什么样:
// C语言示例:内存泄漏
void leak_example() {
int *p = (int*)malloc(100 * sizeof(int));
// 用完了,但忘了 free(p);
// p 指向的内存再也无法释放
}
// Python示例:循环引用导致泄漏
class Node:
def __init__(self):
self.ref = None
def create_leak():
a = Node()
b = Node()
a.ref = b
b.ref = a # 互相引用,引用计数永远不为0
# 函数结束,a和b本应被回收,但因为互相引用,GC也收不走
避坑指南:我曾经在写一个网络库时,用了Python的循环引用,结果服务跑了一周后内存暴涨。后来改用weakref才解决。记住:能用弱引用就别用强引用,这是减少泄漏的黄金法则。
好了,这一章的内容就到这里。内存泄漏这个概念,说白了就是「借了不还」。后面的章节,我会带你一步步学会怎么发现它、怎么修复它、怎么预防它。