为什么要使用垃圾回收器?

18

可能是重复的问题:
C++中的垃圾收集 - 为什么使用?

你好,我读了一些关于垃圾回收器的文章,但有一件事我仍然不明白 - 为什么要使用垃圾回收?

我来尝试解释一下我的想法:

垃圾回收器应该会在没有需要时释放动态分配的内存,对吧?那么,如果你用 C 语言编写程序,你知道是否需要某个内存块,如果不需要,就可以直接销毁它。

那么,既然只需要明智地管理内存分配和释放,为什么还要使用垃圾回收呢?或者是我漏掉了什么重要的东西吗?谢谢。


26
一切都在于“明智决策”。 - mouviciel
7
因为程序员很懒。 - ericvg
一个相关的线程:https://dev59.com/rHE85IYBdhLWcg3w02_z - Péter Török
13
如果我足够聪明,在所有情况下都能正确地管理内存,那么我会用令人惊叹的洛杉矶修辞和深刻的见解来回答问题,而不是发表这条评论。总体情况下,内存管理非常困难。 - David Thornley
2
@b-gen-jack-o-neill:我觉得你已经回答了自己的问题。这不是必要的。一个足够小心的程序员可以手动管理资源,并且可以做得很好。当然,如果你完成了一个资源,立即释放它可能是有优势的(有时候不释放反而更高效!)。但是不用担心这个问题让开发者有更多时间处理更重要的问题。个人而言,我更喜欢C++风格的“智能指针”,它是手动和GC之间的一个不错的折中方案,因为它确定了何时会释放资源。 - Evan Teran
显示剩余4条评论
22个回答

2
在进行复杂项目时,需要调用多个库和你没有编写的外部代码,这时追踪需要释放的对象和被外部库及代码释放的对象变得非常困难。现在有许多工具可以帮助追踪内存泄漏,但这些问题通常是隐蔽的bug,只有在系统运行数小时或数天后才会变得明显。
然而,我同意你的观点。如果我能掌控代码库,我更喜欢使用像c这样我能掌控的语言。但如果必须与外部力量合作,那么拥有一个良好垃圾回收器的语言更加吸引人。

2

如果您一开始就不产生垃圾,那么就不需要垃圾回收。

避免产生垃圾的一种方法是根本不使用动态内存分配。大多数嵌入式程序都不使用动态内存分配。即使在使用动态内存分配的情况下(即使在许多PC程序中),通常也没有真正使用它的原因。(仅仅因为动态内存分配是可能的,并不意味着应该在任何地方使用它。)

避免垃圾回收的另一种方法是使用不将引用内容分离的语言。在这种情况下,实际的内存泄漏甚至都不可能发生。(但当然仍然可能使用过多的内存。)在我看来,高级语言根本不应该涉及“指针”(地址变量)。


无论GC好坏,它都是一个解决方案,用于解决程序员创造的不自然问题:过度使用动态分配内存直到难以管理。虽然你的方法不能直接应用于许多程序,但沿着减少和合并动态内存分配的思路倾向于减少错误、提高性能和内存利用率,并简化程序逻辑(当然是与手动释放内存相比,而不是与GC相比)。 - R.. GitHub STOP HELPING ICE

2
释义:释放不再需要的内存是一个理想的目标,但在所有情况下自动实现并不可能。即使在没有外部输入的情况下(这可能会影响某些数据是否需要),根据内存的完整状态和完整代码来决定某个内存块是否需要等价于停机问题,这对计算机来说是不可能解决的。
同样地,当应用程序规模增长时,平均程序员的大脑也很快无法处理这个问题。在实践中,只有两种情况下才能实现完全正确的内存管理:
1. 问题很简单(例如短暂的命令行应用程序)且程序员足够有纪律性; 2. 程序员是 Donald Knuth。
在所有其他情况下,我们必须使用近似值。垃圾收集器依赖于以下近似值:它检测到不可访问的内存块。它无法确定可访问的块是否会被使用,但是不可访问的块将不会被使用(因为使用意味着到达)。另一个常见的近似值(许多程序员觉得自己足够聪明而使用)是简单地假设他们想到了每个块,然后祈求最好的结果(变体是:教育用户相信内存泄漏是一种功能,并且定期重新启动是正常的)。

1

当您不需要进行实时应用程序(即使您强制执行垃圾收集器,也无法确定它何时完成任务)或者当您不介意完全控制内存时,您可以开发无头模式并几乎确保不会出现内存泄漏。


我个人认为高级语言并不能使开发更快,因为我习惯于使用汇编语言进行编写。(未提及:我从未认真地使用过高级语言。)听起来很愚蠢,是吧?是的。再读一遍你打的东西。 - JUST MY correct OPINION
你说得对,这听起来很愚蠢。即使你的例子有点压倒性和侮辱性(我不认为自己是专业人士也不是新手),我会撤回这句话,因为它对我的答案没有任何价值。 - Ephemere
即使你强制执行垃圾回收,也无法确定垃圾收集器何时会执行其工作。有实时垃圾收集器。请参阅Baker's Treadmill以了解基本思想。 - J D

1

垃圾回收可以更有效率。

为了分配内存,malloc需要四处寻找足够大的连续内存空间。使用紧凑型垃圾回收器,分配内存只需移动指针(或接近它)。

在C++中,通过使用智能指针并严格遵守惯例,您可以在许多情况下安全而清洁地处理内存,而无需垃圾回收器。但是(1)即使使用shared_ptr和weak_ptr,这种方法并不适用于所有情况,(2)引用计数需要在线程之间进行协调,这会带来性能损失。

可用性是更重要的问题,但在某些情况下,垃圾回收比确定性释放内存更快。


可以;还有各种各样的动态内存分配器实现。这仍然是一个好观点。 - Marc Bollinger

1

你知道是否需要一些内存空间,如果不需要,就可以简单地销毁它。

你可以使用类似的论点来证明几乎任何节省劳动力的设备。为什么要编写数学表达式,当你可以直接生成汇编语言?为什么要使用可读字符,当你可以使用二进制?

原因很简单。我与一些领域最好的程序员一起工作。毫不夸张地说,他们中的一些人已经撰写了有关他们领域的书籍。然而,这些人正在使用C++进行编程,并且在内存管理方面犯了错误。当他们犯下这些错误时,很难找到和纠正。为什么让那些才华横溢的人浪费时间做一些机器可以更好地完成的事情呢?

(是的,对于这个问题有好的答案。例如,当你的每个字节的内存都计算在内,所以你不能承受任何垃圾在任何时候。但这通常不是这种情况。)


1
那么,既然你只需要在内存分配/释放方面明智些,为什么要使用GC呢?
问题在于,要足够明智实际上非常困难。如果智慧不够,就会出现内存泄漏或崩溃。这里是计算机自动内存管理中应用智慧的快速指南。
如果你有一个简单的程序(零级复杂度),你可以使用基于堆栈的分配来处理所有事情。这种方式很容易正确地获取内存分配,但它也是一种非常受限制的计算模型(并且你还会遇到堆栈空间的问题)。所以你开始使用堆;这就是“乐趣”的开始。
第一级复杂性是你有指针,其生命周期绑定到堆栈帧。同样,这很容易做到,并形成了许多C++编程的基础。
第二级复杂性是你有引用计数。这是C++智能指针的基础,非常擅长处理几乎所有的有向无环图森林。你可以通过这个实现很多东西,它允许一些计算模型与函数式编程和并行编程非常好地配合使用。
除此之外,还有第三个层次,即垃圾回收。这可以处理任意的内存结构图,但代价是更加占用内存(因为通常不会尝试过早地释放内存)。其中一个主要的成本是分配的内存量往往更大,因为只有在你可以删除内存的点之后,它才真正有资格进行自动删除,如果你足够聪明地找出生命周期的话。

事实上,如果没有其他有用的操作需要使用尚未释放的内存,那么事物不会立即被释放,这并不会使程序更加占用内存。通常,如果一个程序发现自己从操作系统请求了大量内存,那么这是运行垃圾回收循环的信号。 - supercat

0

你只需要聪明一点,没错 ;) 然而,如果你的设计不正确,可能会很容易忽略某些东西。

使用垃圾回收,你不必关心内存,可以更专注于程序的其他部分,从而可能开发出更“快”的程序。


0

正如你所提到的,你可以自己进行垃圾回收。添加垃圾回收器只是让你不必担心它,并且不必花时间编写和测试垃圾回收代码。如果你正在处理一个包含内存泄漏的现有代码库,使用自动垃圾回收器可能比尝试详细了解所有现有代码以查找和修复问题更容易(也更有效)。

话虽如此,我不喜欢在没有内置自动垃圾回收功能的语言中添加它。如果该语言的设计假定开发人员会考虑内存分配和释放,那么(在我看来),从他们身上剥夺这种责任是对开发人员的不利影响。无法精确控制内存何时以及如何释放可能导致不一致的行为。思考和定义动态分配内存的完整生命周期是规划代码的重要部分。没有自动化系统能真正替代仔细而准确的编程(这适用于远不止垃圾回收器)。


Boehm等人收集器的一个好处是它可以用作非常详细的内存泄漏检测器。使用它的方式很简单,只需切换一下模式--甚至不需要编译器开关。当然,一旦你意识到自己管理对象生命周期的能力有多么糟糕,切换到“收集器”模式并再也不用担心了,这样会容易得多。 - JUST MY correct OPINION
@JUSTMYcorrectOPINION:“永远不再担心它”。直到 Boehm GC 泄漏,因为它是保守的... - J D

0

没有垃圾回收器,每当您动态分配某些内容时,您必须跟踪何时不再需要它,并仅在您不再需要它之后销毁它。这可能很困难,特别是当/如果程序的许多不同部分都有指向一个对象的指针,而没有一个知道其他代码可能正在使用它。

那只是理论。实际上,我必须承认,对我来说也没有那样运作。特别是,当(大多数)人意识到他们的代码将使用垃圾回收器时,他们倾向于将内存管理视为不是问题甚至根本不考虑的问题。因此,他们可以迅速开始编码。对于在开始之前就非常了解的小问题,这可能是一个重要的胜利-但对于更大的问题,似乎(至少对我来说)倾向于在真正理解问题之前跳入并开始编写代码。

在我的经验中,缺乏垃圾回收器会让开发者们更加关注对象的生命周期问题。在这个过程中,他们被激励去寻找简单的解决方案来处理对象的生命周期问题,并且通常都能够做到这一点。在这个过程中,他们通常会将代码(不仅是内存管理)简化到一个更加简单、干净和易于理解的程度。
从某种意义上说,这让我想起了两个程序员的寓言。在项目结束时,那些使用垃圾回收机制的代码的管理者们认为这是一个非常好的事情。问题显然比他们意识到的要复杂得多,考虑到代码的复杂性和生命周期问题,没有人能够手动跟踪它们并生成接近无泄漏的代码。
在没有垃圾回收的情况下做同样的事情结束时,反应却相反。他们意识到问题(一般而言)实际上比他们意识到的要简单得多。对象生命周期问题并不像他们预期的那样复杂,编写无泄漏代码也并不特别具有挑战性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接