大白话聊内存泄漏(超级详细,一看就懂)

简介: 内存泄露指程序中已无用的堆内存未被及时释放,持续堆积导致内存占用过高、性能下降甚至崩溃。多见于C/C++等需手动管理内存的语言,而Java、Python等带垃圾回收机制的语言可避免。虽难彻底根治,但可通过工具(如Valgrind、ASan)检测和预防。

内存泄露是一个比较基础,也比较古老的话题,它是指程序中的无用内存持续堆积,但是又没有得到及时释放,从而导致程序内存占用过高,拖慢了运行速度,如果问题严重的话,程序还可能直接卡死。


当然,其它程序以及操作系统也会跟着遭殃,都会因为计算机内存资源的耗尽而变成蜗牛,一动不动。


解决内存泄露的办法也非常简单,就是退出程序,然后重启!


不过对于服务器上的程序,或者计算机的底层组件,频繁地重启是无法接受的,那将导致工作暂停,或者服务中断。所以,大量的内存泄露是一个非常严重的 Bug,我们必须要足够重视,并且着手解决。


在了解内存泄露之前,我们先来看一下程序的内存布局。下图是 32 位 Linux 系统下一个进程的内存分段示意图。



在 32 位环境下,一个程序占用 4GB 的内存,其中「内核空间」是被操作系统占用的,我们没法直接干预;「保留区域」也不用来存储数据,只用作一些特殊目的,比如,你可以让空指针指向这里。


除了这两个区域,剩下的那些内存才是被我们自己编写的程序所占用的,也就是图中的①和②两个部分,这其中:

  • ② 在程序运行期间会一直存在,直到程序销毁才能被释放;
  • ① 在程序运行期间会不断变化,它被不断地分配和释放。


在第①部分中,有一个分段叫做堆(Heap),这才是我们能够随意操作的内存,你可以随时分配,也可以随时释放,属于真正的自由空间。


操作系统不会干预堆区内存,我们只能自己手动管理,如果我们分配了一段内存,后来它用完了,但是我们却没有及时释放,这个时候就会发生内存泄露。


被泄露的内存,在程序运行期间会一直存在,并且会持续积累,直到程序运行结束后才会被操作系统一次性回收。


除了堆区,其它区域的内存都由操作系统管理,要么用完即毁,要么一直存在,从来不会发生内存泄露。


还记得C语言中的 malloc() 和 free() 函数吧,它们就是在堆上分配和释放内存。


下面让我们看一段内存泄露的代码:

#include <stdio.h>
#include <stdlib.h>
int main(){
    int *pOld = (int*) malloc( sizeof(int) );  //内存块1
    int *pNew = (int*) malloc( sizeof(int) );  //内存块2
    pNew = pOld;

    //free(pOld)和free(pNew)只能调用一个
    free(pOld);
    //free(pNew);

    return 0;
}

刚开始 pOld 和 pNew 指向两块不同的内存,但是把 pOld 赋值给 pNew 以后,它们就都指向内存块1了。


这个时候你会发现,内存块2没有指针指向它了!


这意味着,我们永远无法知道内存块2的地址了,它就像消失在堆区的海洋里一样,再也找不到了,再也没法释放了。然而,它又实实在在地存在于堆区之中,不能被其它数据占用。


这种内存丢失,就是一种典型的内存泄露。


上面的代码比较简单,有经验的程序员可以轻松应对,但是在实际开发中,函数之间的调用关系非常复杂,有时候你搞不清应该在什么地方释放内存,所以你干脆就不释放了。


虽然这块内存没有丢失,但是你没有及时释放,它也会成为垃圾内存,随着程序的运行而持续堆积。这也是内存泄露的一种常见情况。


搞清楚了内存泄露的概念,你会发现,内存泄露并不是一种错误,只是一种由于管理不善而导致的潜在隐患。


如果内存泄露的不多,比如只有 10KB,或者 30MB,那么它就不会影响程序运行,问题就不算严重,甚至你都发现不了。有一些比较宽松的公司,可能压根都不把它当做一个问题。


只有当内存泄漏的足够多了,比如 1GB,或者3GB,这个时候才会影响程序运行,我们才必须要去解决。


内存泄露是 C/C++ 中最难发现和解决的 Bug,即使非常有经验的程序员,也不能保证自己的程序没有内存泄露。


帮助解决内存泄露的办法有很多,比如 Valgrind、Windbg、Address Sanitizer(ASan)内存分析工具,以及 mtrace()、ccmalloc() 函数等,但是它们也只能解决一些常见的问题,对于那些复杂或者罕见的问题往往也力不从心。


另外,它们还比较考验程序员的功力,并不是傻瓜式操作。


总之,内存泄露是一个困扰 C/C++ 程序员多年的问题,它无法彻底根治,只能去尽力避免。


后来的很多高级语言,比如 Java、Python、JavaScript、C#、PHP、Go 等,都增加了垃圾内存回收机制,这从根本上避免了内存泄露,程序员再也不用担心内存问题了。


不过垃圾内存回收是有代价的,你需要对内存进行标记,并及时改变它的值,另外还需要额外增加一个线程或者进程,对内存进行实时监控,发现没用的内存就及时回收。

相关文章
|
10月前
|
监控 安全 Unix
iOS 崩溃排查不再靠猜!这份分层捕获指南请收好
从 Mach 内核异常到 NSException,从堆栈遍历到僵尸对象检测,阿里云 RUM iOS SDK 基于 KSCrash 构建了一套完整、异步安全、生产可用的崩溃捕获体系,让每一个线上崩溃都能被精准定位。
3288 170
|
2月前
|
缓存 JavaScript 安全
Claude Desktop 国内安装教程(Windows,2026)
官网Claude安装失败?因引导器需从被墙域名下载MSIX包。推荐使用开源Claude Desktop安装器:自动多源回退下载、SHA256校验、静默安装、补全Node.js与claude-code组件、创建快捷方式,全程离线可用,安全可靠。(239字)
11944 4
|
9天前
|
存储 调度 C++
【保姆级】线程Thread到底是什么?一文彻底搞懂
本文面向C/C++学习者,通俗讲解多线程编程的必要性与核心概念:从多核CPU发展切入,对比进程与线程(以“工厂—生产线”类比),阐明线程是进程内可并发执行的任务单元,强调多线程对提升程序性能的关键作用。
|
1天前
|
Unix 编译器 Linux
主流C语言编译器汇总(超级详细)
本文科普C语言主流编译器:桌面端有Windows专属的MSVC(C标准支持弱)、跨平台开源的GCC与Clang;嵌入式领域则涵盖Keil C51、AVR GCC、ARM DS-5、TI CCS等专用工具链。Arduino和树莓派等平台支持免费GCC/Clang。
|
1天前
|
Unix Linux C语言
C语言的6个冷知识,你肯定没见过,太有意思了
本文分享6个冷门却有趣的C语言知识点:数组下标可写成`index[arr]`、支持`&lt;:`替代`[`等符号替换、`--&gt;`是自减+比较的组合假象、字符串字面量自动拼接、`long`未必比`int`长(取决于平台)、`1 byte`不一定等于8 bit(由`CHAR_BIT`决定)。兼顾趣味性与实用性。
|
1天前
|
存储 编译器 程序员
大白话聊栈溢出(stack overflow),超级详细
Stack Overflow是全球最大的程序员技术问答社区,名字源于程序运行时的“栈溢出”错误。文中深入浅出地讲解了内存中栈的原理、栈帧机制、线程栈限制及栈溢出成因与典型场景。
|
2天前
|
Linux C语言 开发者
MinGW下载和配置保姆教程(零基础搭建C语言环境)
MinGW是Windows下免费开源的GNU工具集,可编译生成原生.exe程序,无需依赖库、绿色轻量。支持跨平台开发,兼容Linux代码,配置简单。本文提供官网最新v14.0.0三版本下载及详细安装、环境变量配置与常见问题解答。
|
9天前
|
Java Windows
【保姆级】Java配置环境变量图文教程,超级详细
本文详细介绍了Windows 10系统下JDK环境变量配置的完整步骤:包括设置JAVA_HOME变量、将%JAVA_HOME%\bin添加至Path路径,以及通过cmd执行java -version命令验证配置是否成功,图文并茂,操作清晰易懂。
|
9天前
|
Ubuntu Unix Linux
【保姆级】POSIX pthread线程库是什么?一文搞懂
POSIX是统一类UNIX系统接口的国际标准,确保程序跨平台可移植;其子集pthread专规范多线程编程接口。遵循POSIX/pthread编写的程序可在Linux、BSD等系统无缝运行,具备标准化、成熟稳定、社区支持强等优势。