为什么其他编程语言没有像Java垃圾收集器那样自动进行垃圾回收?

3

为什么其他编程语言没有垃圾回收器?

为什么这些其他语言没有内置垃圾回收机制?为什么程序员需要负责垃圾回收?


1
那么你想回答哪个问题?是标题中的还是正文中的? - JohnFx
1
@Tom:我很难想象一个后于1990年的语言如果没有某种形式的垃圾回收机制,除了旧版本的语言或汇编语言外,它如何能够繁荣发展。Java之所以拥有垃圾回收机制是有充分理由的。我所能想到的是,Perl 5与Java大致同时期,只有引用计数而没有标记清除的垃圾回收机制。C和C++几乎满足了所有商业需求,可以创建内存泄漏和悬空指针,因此也许应该看一些相对较小众的语言。维基百科称,Ada实现通常不支持垃圾回收,最后一个版本是2005年。 - Steve Jessop
1
与其使用“语言”,我认为这里应该使用“平台”一词。我可以在完全托管和垃圾回收的.NET平台上编写C++代码。我也可以在非.NET平台上编写不受管理的C#代码。 - Travis Heseman
@n0rd:区分不应该是“内存”和“其他”,而应该是“值”、“值持有者”和“实体”。实体通常具有明确的身份和明确的所有者,他们将知道何时不再需要实体。可能会更改的值的持有者通常也具有明确的身份和所有权。然而,不会更改的值通常没有真正的身份或所有者。如果两个对象都具有封装字符序列“Fred”的String字段,则任一对象都可能知道何时不再需要“Fred”,但不应关心... - supercat
除非使用像WeakReference这样的东西来强制String表现为一种实体,否则对象(或其他人)是否正在使用相同的String对象不确定。 除非某个可达引用路径指向它,否则字符串将在瞬间停止存在。GC不会销毁除那些仅由诸如弱引用之类的东西可访问的对象之外的对象; 相反,它回收不存在的对象曾经使用的内存。 - supercat
显示剩余6条评论
10个回答

22

不使用垃圾回收的原因:

  • 直到1985-1990年左右才开发出真正高效的垃圾回收器。如果当时设计语言时考虑了效率,那么就不会有垃圾回收功能。例如:Ada、C、Fortran、Modula-2、Pascal。

  • Bjarne Stroustrup 认为更好的语言设计是使每个成本都显式,并且“不为你不使用的功能付费”。(请参阅他在第二届和第三届编程语言历史ACM会议上的论文。) 因此,C++ 没有垃圾回收功能。

  • 一些研究型语言使用其他思路(如区域,高级类型系统)来明确、安全地管理内存。这些思路对于设备驱动程序等问题具有特殊优势,因为你可能无法负担分配内存,或者对于实时系统,内存成本必须非常可预测。


4
垃圾回收只有助于内存管理。而像RAII这样的模式可以让你以一致的方式确定性地管理所有资源,包括内存。 - Adrian McCarthy

6
硬件没有垃圾回收器(有一些硬件支持转发指针,这是某些垃圾回收器构建中有用的特性,但那远非“硬件中的GC”)。相应地,汇编语言没有垃圾回收器。汇编语言是一种“编程语言”(虽然它是最接近底层的编程语言之一),因此在现有的编程语言广泛的谱系中,有些不会有垃圾回收器。
一个高效的GC不容易实现。好的算法已经成熟了很久。更重要的是,大多数好的GC算法之所以好,是因为它们执行一些复杂的操作,比如移动RAM中的数据元素;这对于提供关于分配的最大时间保证的“实时GC”是必要的(当存在碎片时无法提供这样的保证,而避免碎片又不能移动RAM中的对象)。当对象被移动时,必须自动调整所有指向该对象的指针,这只能在编程语言提供强类型的情况下完成。例如,在C或C++中无法完成此操作。在C中,合法打印编码指针值的字节,然后让用户将其键入。当GC移动对象时,GC无法改变用户的大脑...
实际上,没有强类型的语言是无需垃圾收集器的。这包括C、C++、Forth以及各种具有扩展功能的汇编语言... 这并不妨碍一些人为这些语言编写垃圾收集器实现,例如Hans Boehm's GC for C and C++。然而,这意味着GC可能会失败,对于标准合法的(奇怪的)程序而言。

也有一些具有强类型但没有垃圾收集器的语言,这要么是因为它们的设计者不相信垃圾收集器,要么是因为他们相信在没有垃圾收集器的情况下能做得更好,或者是因为他们害怕额外的代码大小(例如,智能卡的Java - Javacard是无需垃圾收集器的,因为在只有8 kB代码和512字节RAM的环境中安装垃圾收集器是困难的)。

最后,在成千上万的编程语言中(我曾经听说过“自60年代以来每周设计一次”),有些是在夜深人静时谈论酒精过量的结果,因此不能认为所有编程语言的每个特性或非特性都是平衡理性思考的结果。


5
"其他的语言"是这个问题标记为C#,而.NET CLR绝对会执行自动垃圾回收。
我可以想到几个C++没有垃圾回收的原因:
  • C++中所有现有的代码都使用显式内存管理,因此实现垃圾回收将是一个破坏性的变化;

  • 同样地,C++程序员已经习惯了显式内存管理,因此垃圾回收并不是一个非常重要的功能;

  • 好的垃圾回收算法相当新颖,而C++比它们早得多。垃圾回收是一种水平特性,语言设计者必须对规范进行重大(且复杂)的更改。简而言之,在现有语言中添加垃圾收集器比在从一开始就设计语言时更难。

  • Java运行在虚拟机上,.NET使用类似的东西,而C++处理本地代码。在前一种情况下,GC要容易得多。

  • C++经常用于需要在严格内存要求下运行的应用程序(即嵌入式系统),在这些情况下,显式内存管理是必需的。我想某种形式的“选择性”GC可以解决这个问题,但这对于语言设计者来说甚至更难实现。


1
@Aaronaught:嗯,将一个C/C++应用程序转换为使用我在下面链接的Boehm收集器其实非常简单。而且你不应该需要做任何关于文件句柄和套接字的管理,因为在大多数GC语言中,你仍然需要手动管理文件句柄和套接字。您需要一些参考资料吗? - msalib
2
@Aaronaught:请花点时间浏览我之前链接的Boehm GC页面。在那里,您将找到有用的信息,例如“根据经验,这个收集器可以与大多数未修改的C程序一起工作,只需用GC_malloc调用替换malloc,用GC_realloc调用替换realloc,并删除free调用。”再说一遍,您介意以更专业的方式写作吗?您能做到吗?您能不使用讽刺侮辱来反驳别人吗?还是这种技能超出了您的能力范围? - msalib
3
@Aaronaught:我使用过Boehm垃圾回收器,所以你是错误的。我的经验是,即使是最好的开发人员也往往会搞砸手动内存分配,一旦出现问题,数据就会被覆盖,导致非常难以调试的复杂故障。基于这种经验,如果我的应用程序的性能足够好,我很愿意使用Boehm回收器。你可能想多了解一下Boehm……他不只是一个有网页的随机人物。显然,对于像垃圾回收这样基本的东西所做的任何更改都必须进行严格的测试。 - msalib
1
@Aaronaught:任何更改都应该进行测试。你的说法不是将C程序切换到GC是需要进行测试的更改。每个人都同意这一点。你的主张是它会破坏现有的程序。大多数更改不会破坏现有的程序,但我仍然测试我所做的所有更改。你还声称这将非常费力;这也是不正确的。 - msalib
C++和C确实有一个可选择的垃圾回收器:Boehm垃圾回收器。它是一个“作为库的垃圾回收器”。 - James M. Lay
显示剩余8条评论

4
People already answered to your question, but still, your question has an hidden assertion that is "garbage collection is the solution to all problems", and I would like to discussion this assertion...
GC is not the only way to handle memory
There is at least three ways to handle memory allocation:
- Manually - Garbage Collection - RAII We agree that "Manually" can be actually cumbersome and ugly. Now, you should note that even with a GC, there are some devious ways to leak memory.
GC does not handle resource leaks
There are a lot of limited resources in a program, in addition to memory:
- file handles - other OS handles - network connections - database connections - etc.

这些是有限的资源,你希望它们在不再使用时尽快被释放,而不是完全不释放或者仅在进程退出时才释放。

在受GC影响的语言中(如Java),这些资源通常必须手动获取和释放。如果你想看看它有多么丑陋,请看看这个问题:

Java中的RAII...资源处理总是如此丑陋吗?

RAII可以处理内存和资源泄漏

使用RAII习惯将使您编写可读性强的代码,没有任何泄漏,无论是内存还是其他方面。事实上,我无法记得自己曾经担心过内存分配/释放的时间,尽管我没有使用垃圾回收。

但我清楚地记得在2008年10月,我不得不解决Java中的资源泄漏问题,解决方案是如此丑陋,以至于令人厌恶。

结论

为什么其他语言没有垃圾回收机制?

答案可能是:

  • 因为当时GC的效果不够好
  • 因为GC并不是解决所有资源泄漏问题的方案
  • 因为没有必要。

C++更多地属于“没有必要”的部分。

GC可以成为C++ RAII的一个很酷的补充(请参见C ++中的垃圾回收 - 为什么?),但我绝不会用你的GC来交换我的RAII。

永远不会。


我已经用几种语言编写了一个递归程序,仅计算到短小的小数位数的Pi值,所有带有垃圾回收机制的语言似乎都无法在200毫秒内更快地计算它。现在,使用一些没有垃圾回收机制的语言,我可以获得无法测量的0毫秒时间。因此,如果您需要快速/频繁地获取任务重型操作的结果,请使用没有垃圾回收机制的语言。 - mjwrazor

3

一些编程语言比较古老,例如C语言,最初是为比今天慢得多的机器上的系统编程而设计的。当时可能还不存在垃圾回收(也许Lisp除外?),即使有,设计师们也不想在程序员自己可以完成的情况下花费所有的CPU循环和内存开销来执行垃圾回收。由于那时的计算机性能远不如今天,软件也相对简单,因此程序员手动管理内存比在现在可能编写的更大型应用程序中更容易。


6
你是说“或许是Lisp”?没错,就是Lisp。Lisp比C语言更古老。 - Tom Hawtin - tackline
1
@Tom:虽然Lisp更容易进行垃圾回收(并且具有高效的发射代码),所以它不仅仅是年龄问题。如果在C语言发明时,命令式语言中的垃圾回收也如此简单,那么Sun公司不会花费近20年时间改进Java GC从第一个版本开始。他们会毫不费力地将分代、压缩、并发、实时保证GC随手拿出来使用。 - Steve Jessop
2
Steve Jessop:你能解释一下为什么生成GC友好的Lisp代码比C代码更容易吗?我的意思是,大多数Lisp实现都编译成基本上是可移植汇编语言的中间语言,而这个汇编语言基本上就是C。Lisp是一种命令式语言。至于Sun,我通常不会接受从大公司理性和完美执行的前提开始的论点。 - msalib
1
@Aaronaught:中间语言用于JIT编译,但也广泛用于静态编译;这就是为什么GCC有几个不同的IR。此外,一些Lisp实现直接编译成C并使用系统C编译器构建结果图像。关于已编译应用程序和操作系统处理GC的问题,我不知道你在说什么...有人提到过吗?显然,Common Lisp运行时与libc不完全相同,但是...那又怎样? - msalib
1
Steve Jessop,我从来没有打算暗示“因为Lisp可以编译,所以任何汇编程序都可以进行垃圾回收”……我甚至不确定那是什么意思。我的观点只是很多Lisp实现使用类似C的(在某些情况下确切地说就是C)中间表示形式。而且很多实现还使用了与C代码非常良好地互操作的外部函数接口。 - msalib
显示剩余19条评论

2

一个简单的事实是:并不存在银弹。垃圾回收并不能解决所有的内存/性能问题。


2
如果你不知道为什么,那是因为钱的原因。早期计算机昂贵而程序员便宜。现在情况完全相反-计算机廉价而程序员昂贵。垃圾回收器需要一些CPU来完成它的工作。
此外,大多数垃圾回收器需要通过暂停程序执行进行完整的扫描。在实时软件(如工业监控和股票交易)中,这是不可接受的。有时候在我共同开发的某些应用程序中(例如ASP.NET网站),客户也可能会看到应用程序偶尔会冻结一分钟左右。
另一个原因是没有人是完美的,垃圾回收器可能会出现一些泄漏。如果使用一些非GC语言小心编写,则不太可能出现此类情况。

实际上,普通的“程序员”现在比以往任何时候都便宜... 称职的 程序员则是另外一回事。 - Lawrence Dol

0

像C#和Java这样的现代语言具有垃圾回收功能,因为如果您不必担心内存管理,编写代码会更容易。而旧的语言则没有这个功能。此外,还有许多应用程序(例如在没有任何虚拟内存访问权限的嵌入式应用程序中运行),您需要精确地管理应用程序将使用多少内存,对于这些情况,C++等语言更合适。实时应用程序也可能限制您使用垃圾回收语言的能力,因为您需要完全控制应用程序在任何时候的响应速度。


1
实时参数似乎不正确:大多数C/C++分配器没有保证的实时性能边界,并且有实时GC实现可用。如果您正在进行实时工作,则需要使用特殊的内存管理实现,无论您使用什么语言。 - msalib
我认为原文写得很正确:“实时应用可能会限制您使用垃圾回收语言的能力”。是的,我同意,即使您使用非GC语言,在代码的时间关键部分仍需要进行工作以避免内存分配和任何其他长时间运行的操作。 - Ian Mercer
2
我想实时参数是非常正确的。以视频游戏为例,运行使用垃圾回收(例如Lua)的脚本语言可能会在分配内存时触发垃圾回收。这可能会导致CPU飙升并花费超过一帧的时间来执行。这将导致可见的帧率延迟。 - Cthutu
1
@Cthutu 但这与垃圾回收没有特别的关系。这适用于任何可能导致无限暂停的库。垃圾收集器可以,但不一定要这样做... - J D
@Ian - 旧语言确实有垃圾回收机制。事实上,第二古老的仍在使用的编程语言Lisp就发明了垃圾回收机制。 - Cthutu
@Jon - 你可以这样概括。你也可以使用增量垃圾收集器,但是在使用它们时仍需要小心,并正确调整它们以避免内存峰值。到目前为止,我见过的最好的系统是Objective-C中的ARC,它将两者的优点结合起来:确定性内存使用与(几乎)具有GC编程感觉。 - Cthutu

0

C、C++、Java 和 C# 是在不同的时间点创建的(并按照这个顺序)。Java 和 C# 都有垃圾回收机制。

通常,最近开发的语言往往具有更好的内存管理支持,因为技术水平每次都会有所提高。


2
考虑到Common Lisp和Smalltalk是在C和C++之前开发的,这并没有太多意义。 - msalib
@msalib 请参考Steve Jessop在此回答中的评论:https://dev59.com/WkzSa4cB1Zd3GeqPnpGq#2444829 - Tyler
@msalib:这是虚假的,就像你在这里发表的所有其他评论一样。 C语言的开发始于与Smalltalk相同的年份,而C语言早在大众市场上就出现了。 Lisp针对完全不同的市场;它本身是/曾经是一种很棒的语言,但几乎与此答案或一般主题无关。 - Aaronaught
@msalib:我认为,更近期的编程语言往往对内存管理有更好的支持。实际上,我是Smalltalk的早期用户,并且非常熟悉它。然而,同一时期的绝大多数编程语言都没有提供垃圾回收功能。而在过去几年中创建的绝大多数编程语言都提供了垃圾回收功能。仅仅因为你能指出一个吸烟120年的老人并不意味着重度吸烟会让你长寿。 - Eric J.
谢谢您澄清。我不确定您认为我们应该得出什么推论:C语言是一种古老的语言,这个事实为什么可以解释它为什么没有垃圾回收呢?显然这并不是全球对垃圾回收存在的无知。 - msalib

0

实际上,C和C++都有垃圾回收机制在这里

但总的来说,C/C++社区从一开始就对许多成功的编程语言特性产生了厌恶情绪,这些特性在动态语言社区广泛使用,其中包括GC。我认为这种现象部分源于贝尔实验室的文化;你会发现一群核心、聪明、努力的人们,他们坚信自己知道得更好,不需要任何语言特性来降低缺陷率。这就是为什么C字符串仍然是一个噩梦般的安全漏洞,今天仍然造成着巨大的安全问题:因为一群贝尔实验室的黑客知道,他们可以编写安全的代码,即使API由剃刀和硝化甘油制成。完美的程序员不需要网络字符串,完美的程序员也不需要垃圾回收机制。可惜没有完美的程序员。自信很重要,但谦卑可以防止我们自我毁灭。


1
在 C 和 C++ 设计时,几乎所有使用 GC 的严肃语言都是动态类型的。对吧? - msalib
1
@msalib:正如我回复你的另一条评论所说,Smalltalk并不是在C之前存在的,而C++是以C为模型而设计的,而不是Smalltalk(显然)。我们真的需要一个垃圾收集器来管理SO。 - Aaronaught
1
@Aaronnaught:Smalltalk 和 C 是大约在同一时间开发的。Lisp 比 C 和 C++ 都要早得多。由于 GC 是 Lisp 和 Smalltalk 的重要特性,因此认为 GC 没有出现在 C/C++ 中是不合理的,因为它是一项新颖的技术,没有人能理解。至于我是谁,我是一个曾在贝尔实验室实习过的人。那里充满了聪明的人,其中一些人有点傲慢。这并不是一个新奇的观察。 - msalib
@Aaronaught:我认识一些在Symbolics工作过的人。其中一个人为非Lisp机器硬件开发了Symbolics的第一个Lisp编译器。那个由两个人在三个月内匆忙组合而成的第一个编译器,虽然缺少很多优化,但在性能方面远远超过了Lisp机器硬件。因此,即使在30年前,你实际上也不需要定制硬件来使Lisp GC快速。 - msalib
所以你正在重述两年前听到的关于30年前某事的故事,而我们实际上正在谈论40年前的情况。此外,似乎没有任何记录,并且甚至没有回答相同的问题(这是关于Lisp和C之间的区别,而不是Lisp机器上的Lisp与非Lisp机器上的Lisp)。这不是我所说的可靠历史证据。我坚持我的原始断言:在当时已知的技术条件下,垃圾收集在传统的1970年代硬件上根本不可行。 - Aaronaught
显示剩余8条评论

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