C/C++ 语言具有其他语言所不具有的灵活性,然而这种灵活性是建立在其复杂的语法上的,相对的,也带来了一些问题。比如 C/C++ 中令人头疼的内存泄露问题, 由于 C/C++ 可以直接对内存进行操作,内存的申请与回收得依靠用户的自觉,因此导致的内存泄露问题也常常被认为无法避免,本文总结了C++ 中可能导致的内存泄露的情况 以及 常见的内存泄露的检测机制。

内存泄露 vs 内存溢出

内存溢出(out of memory) : 程序在申请内存时,没有足够的内存空间供其使用。一般发生内存溢出时,程序讲无法进行,强制终止。
内些泄露(memory leak): 程序在申请内存后,无法释放已经申请的内存空间,内存泄露的积累将导致内存溢出。

常见的内存动态管理错误包括:

申请和释放不一致

由于 C++ 兼容 C,而 C 与 C++ 的内存申请和释放函数是不同的,因此在 C++ 程序中,就有两套动态内存管理函数。一条不变的规则就是采用 C 方式申请的内存就用 C 方式释放;用 C++ 方式申请的内存,用 C++ 方式释放。也就是用 malloc/alloc/realloc 方式申请的内存,用 free 释放;用 new 方式申请的内存用 delete 释放。在上述程序中,用 malloc 方式申请了内存却用 delete 来释放,虽然这在很多情况下不会有问题,但这绝对是潜在的问题。

申请和释放不匹配

申请了多少内存,在使用完成后就要释放多少。如果没有释放,或者少释放了就是内存泄露;多释放了也会产生问题。上述程序中,指针p和pt指向的是同一块内存,却被先后释放两次。

释放后仍然读写

本质上说,系统会在堆上维护一个动态内存链表,如果被释放,就意味着该块内存可以继续被分配给其他部分,如果内存被释放后再访问,就可能覆盖其他部分的信息,这是一种严重的错误,上述程序第16行中就在释放后仍然写这块内存。

java 中的内存泄露与溢出

java 语言机制中虽然有了GC会自动回收内存,但是小伙伴们呐, 不能过分相信GC啊,到时候出了问题, GC可不负责,通宵加班debug的就是你,所以编写代码的过程中还是要保证代码的质量,不能过分的依赖GC。

java 中的内存溢出

  1. 在程序中存在死循环,或循环过多,而产生了过多重复的对象的实例。
  2. 存在对象的引用,使用完后没有清除,导致java 虚拟机不能回收
  3. 一次操作时,在内存中加载了大量的数据

java 中的内存泄露

  1. 长生命周期的对象持有短生命周期的引用

    解决方案: 讲短生命周期的对象 将为 局部变量, 或在引用完毕后手动设置为null

Java 中防止内存泄露:

  1. 当心集合类,比如 HashMap,ArrayList等,因为这是最容易发生内存泄露的地方.当集合对象被声明为static时,他们的生命周期一般和整个应用程序一样长。
  2. 注意事件监听和回调.当注册的监听器不再使用以后,如果没有被注销,那么很可能会发生内存泄露.
  3. “当一个类自己管理其内存空间时,程序员应该注意内存泄露.” 常常是一个对象的成员变量需要被置为null 时仍然指向其他对象,

C++ 中检测内存泄露的方案

不可否认,内存泄露是在开发大型程序中最令人头疼的问题,刚刚踏入C++ 坑的小白我,在公司实习的时候,听到最多的就是,“XXX, 昨天测试跑出来,你的模块泄了。。。”,接着就是那个xxx的苦逼 debug, 以至于有人说,内存泄露是无法避免的。其实不用太担心,虽说没有办法保证 100% 的不会发生泄露,但是我们结合各种检测手段,在coding 的时候多留个心眼,还是可以将概率降到最低的。

在一个单独的函数中,每个人的内存泄露意识都是比较强的。但很多情况下,我们都会对malloc/free 或new/delete做一些包装,以符合我们特定的需要,无法做到在一个函数中既使用又释放。这个例子也说明了内存泄露最容易发生的地方:即两个部分的接口部分,一个函数申请内存,一个函数释放内存。并且这些函数由不同的人开发、使用,这样造成内存泄露的可能性就比较大了。这需要养成良好的单元测试习惯,将内存泄露消灭在初始阶段。

另外我们可以使用 各种检测工具,对代码进行动态检测 和 静态检测,查出存在的泄露点或潜在泄露点。

静态检测

所谓静态检测,就是不运行程序,在程序的编译阶段进行检测,主要原理就是对 new 与 delete, malloc 与 free 进行匹配检测,基本上能检测出 大部分 coding 中因为粗心导致的问题。
静态检测包括手动检测和静态工具分析,所谓手动检测嘛,就是叫大牛帮你review啦。下面介绍了常见的静态检测工具。常用的静态检测的工具有 splint, PC-LINT, BEAM 等,每种检测各有千秋,具体使用方法这里不在赘述,网上教程挺多的,也可以直接去看官方说明文档。但是静态检测不能判定跨线程的内存申请与释放。这时候就需要动态检测出场了。

动态检测

所谓动态检测,就是运行程序的过程中,对程序的内存分配情况进行记录并判定。常用的工具有 valgrind, Rational purify 等,没种工具有各自的特点,大家看情况自行选用。对于动态检测来说,最大的弊端就是会加重程序的负担,对于一些大型工程,涉及到多个动态库,带来的负担太重,这时候就需要自己根据需求写一套了。