内存泄漏能走多远?
Posted
技术标签:
【中文标题】内存泄漏能走多远?【英文标题】:How far can memory leaks go? 【发布时间】:2013-03-06 05:27:08 【问题描述】:我多次遇到内存泄漏。通常当我malloc
-ing 就像没有明天一样,或者像脏衣服一样晃来晃去FILE *
s。我通常假设(阅读:绝望地希望)至少在程序终止时所有内存都被清理了。是否存在程序终止或崩溃时无法收集泄漏内存的情况?
如果答案因语言而异,那么让我们关注 C(++)。
请注意短语的双曲用法,“就像没有明天”和“悬空......就像脏衣服一样”。不安全* malloc
*ing 会伤害您所爱的人。此外,请谨慎使用脏衣服。
【问题讨论】:
如果你运行的是像 Linux 或 Windows 这样的“现代”操作系统,那么当程序终止时,操作系统本身会解析任何未释放的内存。 不要像没有明天那样 malloc-ing,试着假装有明天并记录你的记忆! @WilliamPursell 啊,所以你说应该calloc
就像没有明天一样。很棒。
“如果答案因语言而异,那么让我们专注于 c(++)。” c 和 c++ 不是同一种语言!
@zhermes:关于 C 和 C++ 是不同语言的评论比你想象的要隐藏更多......在 C++ 中,你宁愿发现自己利用具有自动存储持续时间的对象,遵循 RAII 习语......您让这些对象为您处理内存管理。
【参考方案1】:
没有。操作系统在进程退出时释放进程持有的所有资源。
这适用于操作系统维护的所有资源:内存、打开的文件、网络连接、窗口句柄...
也就是说,如果程序在没有操作系统的嵌入式系统上运行,或者在非常简单或有缺陷的操作系统上运行,则内存可能在重新启动之前无法使用。但如果你处于那种情况下,你可能不会问这个问题。
操作系统可能需要很长时间才能释放某些资源。例如,网络服务器用来接受连接的 TCP 端口可能需要几分钟才能空闲,即使被程序正确关闭。网络程序也可能拥有远程资源,例如数据库对象。当网络连接丢失时,远程系统应该释放这些资源,但它可能需要比本地操作系统更长的时间。
【讨论】:
RTOS 中的一个常见范例是单进程、多线程模型,并且“任务”之间没有内存保护。通常是一堆。这当然是 VxWorks 过去的工作方式 - 并且可能现在仍然如此。 请注意,并非所有资源都可以被操作系统释放。网络连接、数据库事务等,不明确关闭它们可能会导致一些不良结果。不关闭网络连接可能会导致服务器认为您无限期地仍然处于活动状态,而对于限制活动连接数量的服务器,可能会意外导致拒绝服务。不关闭数据库事务可能会导致您丢失未提交的数据。 @Marko : vxWorks 的最新版本现在支持支持内存保护的 RTP(实时进程)。 “操作系统在进程退出时释放进程持有的所有资源。” 严格来说并非如此。例如,在(至少)Linux 上,SysV 信号量和其他 IPC 对象在进程退出时不会被清除。这就是为什么有ipcrm
用于手动清理,linux.die.net/man/8/ipcrm 。
另外,如果一个对象有一个它维护的临时文件,那么显然之后不会被清理。【参考方案2】:
C 标准没有规定在程序终止时释放由malloc
分配的内存。这是由操作系统完成的,并非所有操作系统(通常是嵌入式操作系统)都会在程序终止时释放内存。
【讨论】:
这或多或少是因为 C 标准谈论的是 C 程序,而不是 C 恰好运行的操作系统...... @vonbrand C 标准可能有一个段落说当main
返回时,malloc
分配的所有内存都被释放。例如,它说所有打开的文件在程序终止之前都已关闭。对于分配给我的malloc
的内存,只是没有指定。当然,现在我关于操作系统的句子描述了通常所做的事情,而不是标准规定的事情,因为它没有对此做出任何规定。
让我更正我的评论:标准谈论 C,而不是程序如何启动和停止。你可以很好地编写一个没有操作系统的C程序。在这种情况下,没有人会进行清理。标准 very 故意不指定任何内容,除非需要,以免限制不必要的使用。
@ouah:“当 main 返回...”。这是一个假设。我们必须考虑“如果 main 返回...”。 std::atexit
还考虑通过std::exit
终止程序,然后还有std::abort
和(C++ 特定)std::terminate
。
@ouah:如果包含在内,atexit
将无法使用。 :-)【参考方案3】:
因为所有答案都涵盖了您问题的大部分方面。现代操作系统,但从历史上看,如果您曾经在 DOS 世界中编程过,那么有一个值得一提。 Terminant and Stay Resident (TSR) 程序通常会将控制权返回给系统,但会驻留在内存中,可以通过软件/硬件中断恢复。在这些操作系统上工作时,看到诸如“内存不足!尝试卸载一些 TSR”之类的消息是正常的。
从技术上讲,程序终止,但由于它仍然驻留在内存中,除非您卸载程序,否则不会释放任何内存泄漏。
因此,除了操作系统不回收内存之外,您可以认为这是另一种情况,因为它存在错误或嵌入式操作系统旨在这样做。
我还记得一个例子。 Customer Information Control System (CICS),主要在 IBM 大型机上运行的事务服务器是伪对话的。执行时,它处理用户输入的数据,为用户生成另一组数据,传输到用户终端节点并终止。激活注意键后,它会再次恢复处理另一组数据。因为它的行为方式,再次从技术上讲,操作系统不会从终止的 CICS 程序中回收内存,除非您回收 CICS 事务服务器。
【讨论】:
这真的很有趣,感谢您的历史记录!您是否知道该范式是否是由于在不必要的情况下释放内存的计算成本太高?还是从未想到过替代方案? @zhermes:这在计算上是不可能的,因为 DOS 根本不跟踪 TSR 的内存分配。几乎按照定义:目标是Stay Resident。如果您希望您的 TSR 释放一些但不是全部内存,则由您决定要释放什么。 @zhermes:DOS(就像它的前身 CP/M)并不是现代意义上的操作系统。它实际上只是一组 I/O 实用程序,可以以标准方式调用,并与命令处理器捆绑在一起,让您一次运行一个程序。没有进程的概念,内存既不是虚拟的,也不是受保护的。 TSR 是一种有用的 hack,它可以告诉系统它们占用了多达 64K 的空间,并将自己挂在中断中以便它们被调用。【参考方案4】:正如其他人所说,大多数操作系统会在进程终止时回收分配的内存(可能还有其他资源,如网络套接字、文件句柄等)。
话虽如此,在处理 new/delete(而不是原始 malloc/free)时,内存可能并不是您唯一需要担心的事情。在 new 中分配的内存可能会被回收,但可能在对象的析构函数中完成的事情不会发生。也许某些类的析构函数在销毁时将标记值写入文件。如果进程刚刚终止,文件句柄可能会被刷新并回收内存,但不会写入该标记值。
故事的寓意,总是要自己清理。不要让事情悬而未决。不要依赖操作系统进行清理。自己清理干净。
【讨论】:
'不要依赖操作系统清理你。自己清理干净。对于复杂的多线程应用程序,这通常是“非常、非常困难”的。对资源的所有引用都已丢失的实际泄漏是不好的。允许操作系统清理而不是显式释放引用并不总是坏事,而且通常是唯一合理的做法。 在 C++ 中,析构函数将在程序终止时被调用(除非出现一些不太亮的kill -9
粉丝...)
@vonbrand 是的,但如果我们谈论的是动态对象的泄漏,那些析构函数就不会发生。超出范围的对象是一个原始指针,它的析构函数是一个空操作。 (当然,请参阅 RAII 对象以缓解此问题...)
RAII 的问题在于它坚持在进程退出时解除分配对象,实际上摆脱它并不重要。您要小心处理的数据库连接,但一般内存最好由操作系统清理(它做得更好)。该问题表现为一个程序,该程序需要 绝对年龄 才能在分页的内存量增加后退出。解决起来也很不容易……
@vonbrand:没那么简单。 std::exit
将调用 dtors,std::abort
不会,未捕获的异常可能。【参考方案5】:
这更可能取决于操作系统而不是语言。最终,任何语言的任何程序都会从操作系统中获得它的内存。
我从未听说过在程序退出/崩溃时不回收内存的操作系统。因此,如果您的程序对需要分配的内存有上限,那么只分配而不释放是完全合理的。
【讨论】:
在简单的操作系统的情况下,你能搞砸内核的内存图片吗?......就像那些甚至没有多任务处理的操作系统。 @ulidtko,这会搞砸了。如果我的程序偶尔需要说 1GiB,并在这段时间内抓住它,那么即使不使用这些资源,它也会拒绝其他人使用这些资源。这在今天可能很重要,也可能不重要。但环境将彻底改变。保证。 @vonbrand 很少使用 1GiB 通常不是问题(只要您有足够的物理内存),因为现代操作系统可以分页出当前不活动的位。当您活动使用的虚拟内存多于托管它的物理内存时,问题就出现了。【参考方案6】:如果程序曾经变成一个动态组件(“插件”)并加载到另一个程序的地址空间,这将是很麻烦的,即使在具有整洁内存管理的操作系统上也是如此。我们甚至不必考虑将代码移植到功能较弱的系统。
另一方面,释放所有内存可能影响程序清理的性能。
我正在处理的一个程序,某个测试用例需要 30 秒或更长时间才能退出程序,因为它正在遍历所有动态内存的图形并逐块释放。
一个合理的解决方案是拥有该功能并用测试用例覆盖它,但在生产代码中将其关闭,以便应用程序快速退出。
【讨论】:
【参考方案7】:所有当之无愧的操作系统都将清理您的进程在终止后造成的混乱。但是总是有不可预见的事件,如果它以某种方式被拒绝访问并且一些可怜的程序员没有预见到这种可能性,所以它不会稍后再试一次怎么办? 如果内存泄漏对任务至关重要,那么清理自己总是更安全 - 否则如果付出代价高昂,IMO 就不值得付出努力。
编辑: 如果内存泄漏发生在它们会累积的地方,你确实需要清理它们,比如在循环中。我所说的内存泄漏是在整个程序过程中不断累积的,如果你有任何其他类型的泄漏,它很可能迟早会成为一个严重的问题。
从技术上讲,如果您的泄漏是内存“复杂性”O(1),在大多数情况下它们都很好,O(logn) 已经令人不快(在某些情况下是致命的)并且 O(N)+ 无法忍受。
【讨论】:
【参考方案8】:POSIX 兼容系统上的共享内存一直存在,直到调用 shm_unlink 或系统重新启动。
【讨论】:
【参考方案9】:如果您有进程间通信,这可能会导致其他进程永远不会完成和消耗资源,具体取决于协议。
举个例子,当我在打印机作业的中间终止 JVM 时,我曾经尝试用 Java 打印到 PDF 打印机,PDF 假脱机进程仍然处于活动状态,我不得不在任务管理器中终止它在我可以重试打印之前。
【讨论】:
以上是关于内存泄漏能走多远?的主要内容,如果未能解决你的问题,请参考以下文章