垃圾收集真的有用么?



http://bbs.chinaunix.net/thread-4097113-1-1.html


只要写程序, 就免不了要和各种资源打交道, 其中最频繁的莫过于内存了.

任何一个程序都需要内存管理. 它不管是简单的还是复杂的, C 语言的还是 java 语言的. 所不一样的是, 内存管理的细节掌握在谁的手里.

对于 C 语言, 毫无疑问的, 程序员掌握所有细节. 程序员获得了最大的灵活性, 作为代价, 编译器不对任何内存管理上的疏忽负责. 而人是 最容易犯错 的生物. 意味着, 程序员总会犯错, 因此很难写出在内存管理上没有瑕疵的程序.

毫无疑问的, 将内存管理的重担全部丢给程序员, 是编译器水平低下的时代无奈的选择. 随着编译器技术的发展, 将内存管理的任务从程序员手中接管是必然的.

对于如何接管内存管理, 语言作者们分成了截然不同的两派

    垃圾收集
    RAII (Resource Acquisition Is Initialization) + 智能指针

在带有垃圾收集的语言里, 程序员只管分配内存, 无需操心释放. 垃圾收集器间歇性的运作, 会将不再使用的内存释放掉. 至于如何标记哪些内存是不再使用的, 几十年间发展出了各种算法. 许多语言都带有多种标记算法供选择. "没有哪一种垃圾收集策略是适合所有程序的, 所以各种语言都发展出多套垃圾收集器, 供运行时选择."

    在许多语言里, 垃圾收集并不是编译器实现的, 而是由语言附带的运行时环境实现的, 编译器为运行时提供了附加的信息. 这就导致了语言和运行时的强耦合. 让人无法分清语言的特性和运行时的特性.

垃圾收集不是完美的, 使用垃圾收集并不意味着就可以高枕无忧了. 垃圾收集并不意味着内存泄漏成为过去式, 倒是野指针确实成为了过去式, 因为只要还有指针引用一个对象, 这个对象就绝对不会被释放. (不过, 带有垃圾收集的语言或多或少都废除了指针吧, 用引用替代了指针)

有很多很多复杂的原因丢会导致垃圾收集器无法回收特定的内存, 导致这部分内存泄漏. 更严重的是, 你很难将内存泄漏和还未被清除的内存完全区别开来. 到底是延迟收集策略 还是真的发生了内存泄漏 ? 你永远都无法正确分辨.

结果是, 程序员最终不得不回到 C 语言的老路上, 小心的检查所有的内存分配, 确保没有触发垃圾收集器的bug或者特定的一些策略 . 几乎所有使用带垃圾收集的语言开发的程序, 在其开发的后期都要经历惨痛的 "内存检查" , 回顾所有可能导致内存泄漏的代码.

垃圾收集器的另一个问题是, 除了内存, 它无法对程序使用的其他资源执行垃圾收集. 垃圾收集是以内存管理为目标产生的, 只能收集不再使用的内存, 而不能收集程序使用的其他资源, 如消息列队, 文件描述符, 共享内存, 锁.等等. 程序员不得不对其他资源执行手工管理, 像 C 程序员那样小心翼翼的操作.
最终垃圾收集仍然没有解决 "人容易犯错" 的问题, 还是把其他资源的泄漏问题丢给了程序员.

C++ 从来不认为垃圾收集是有用的东西, 和 C 派不一样 , C 派不喜欢垃圾收集纯粹是因为喜欢 "自己控制一切" (天生的 M 属性). C++ 派同样认为, 要把程序员从资源管理的重担里解放出来. 同 "投机取巧" 的 GC派不同, C++ 做了很多思考, 并最终经历了 30年的时间终于找到了解决的办法. 写入了 C++11 标准.

在这30年的时间里, C++ 的资源管理是逐步发展的. C++11 最终提出的智能指针, 源于 C++30年的探索.

C++ 要想实现 RAII + 智能指针, 两大技术缺一不可 1. 自动确定并调用 构造函数和析构函数 2. 模板

C++ 的第一步试图解放资源管理重任, 是为 C 加入了构造函数和析构函数. 构造函数和析构函数由编译器调用, 生命期终止的对象会自动调用析构函数. 不管生命期终止的原因是 return 返回, 还是 抛出了异常, 编译器总是保证, 生命期终止的对象一定会被调用析构函数.

以 "编译器自动保证对象生命期" 的技术依托下, C++ 发明了 RAII 技术, 将资源的管理变成了对象的管理,而自动变量 (创建在栈上的对象, 类的成员变量) 的生命期由编译器自动保证, 只要在构造函数里申请资源, 在析构函数里正确的释放资源, RAII 技术解决了一大部分的资源管理问题.

模板的引入使得 RAII 技术得以 "一次实现到处使用". 如实现一次 std::vector 就可以到处使用在需要数组的情况下, 而无需为每种类型的分别实现数组RAII类. STL 内置了大量的容器, 几乎满足了所有的需求. STL的容器无法满足需求的情况下, 程序员仍然能借用 STL 的理念实现自己的 RAII 容器.

但是, 如果对象分配于堆上, 程序员不得不手工调用 delete 删除对象. 忘记将不用的对象 delete 就成了头号资源泄漏原因.

如果指针也是自动对象就好了.

C++ 标准的第一次尝试是纳入 std::auto_ptr . 但是效果并不好, 不是所有指针都可以为 auto_ptr 所代替. 最要命的是, STL 容器无法使用 auto_ptr.

C++ 标准的第二次尝试就是纳入了 std::shared_ptr , shared_ptr 在进入 C++11 标准之前, 已经在 Boost 库里实践了相当长的时间.

首先得益于 C++ 的模板技术, shared_ptr 只需实现一次, 即变成可用于任何类型的指针. 其次, 得益于 C++ 的自动生命期管理, 智能指针将需要程序员管理的堆对象也变成了能利用编译器自动管理的自动变量.

也就是, 智能指针彻底的将 delete 关键字 变成了 shared_ptr 才能使用的内部技术. 编译器能自动删除 shared_ptr 对象, 也就是编译器能自动的发出 delete 调用.

模板是智能指针技术必不可少的一部分, 否则要利用 RAII 实现智能指针就只能利用 "单根继承" 这一老土办法了. 没错, 这也是 MFC 使用的. ( MFC 诞生在 模板还没有加入 C++ 的年代. )
直到 1998 年 C++ 标准纳入了模板, C++ 才最终具备了实现自动内存管理所必须的特性.
但是准备好这些特性, 到利用这些特性发明出真正能用的智能指针, 则又花了13年的时间. ( 2011年加入了 shared_ptr. )
发明出编译器实现的自动内存管理需要时间, C++ 花了 30年的时间. 没有耐心的语言走了捷径, GC 就是这条捷径.


——————————————华丽的分割线————————————————


的确,智能指针+RAII可以起到内存管理的功能,但是还是有一个很严重的问题:循环引用。

别觉得这是小事儿,当年COM就死在这上面。

解决循环引用的技术已经有了,而且C++11也自带,就是所谓的“弱引用”,然而,这仍然需要程序员的介入。

换言之,程序员仍然需要关注对象之间的关系,从而给出最适合的说明:是强引用?还是弱引用?一不小心就是循环引用,最终依然无法释放。

而且,垃圾回收没法解决的问题(无意地内存保留),智能指针依然无法解决——如果程序员无意中在某个地方放了一个强引用,则内存无法回收。

垃圾回收比起智能指针的缺点其实就只有一个:垃圾回收是惰性的,而智能指针是随时回收的。即,垃圾回收会导致内存会有一定的“冗余”,而智能指针不会,总是最精确的回收应该回收的数量。

然而,这个缺点也随着垃圾回收的技术进步变得越来越弱。

要注意,内存是重要的资源,内存管理几乎一定是随着逻辑相关的。对智能指针来说,这个逻辑需要人来指定(强引用?弱引用?)而对于垃圾回收来说,这个逻辑是根据垃圾回收的技术来处理的——当然,垃圾回收里面也存在“弱引用”的概念,但是这已经不是必要的了。

其实说白了,所有的资源管理,都是“在必须的时间里,释放掉不需要的资源”,智能指针是不需要就马上释放。垃圾回收是发现是没用的就马上释放。显然,由运行时发现肯定比由语言说明不需要更加简单。垃圾回收不适合其他资源,仅仅在于有些资源是必须立刻释放的。

对于这种东西,增加RAII机制即可(如Java/C#/Python的with语句),依然不需要像C那么“小心翼翼”。

不是说智能指针不好,也不是说垃圾回收就是未来。是说,总有适合的技术。垃圾回收概念简单,算法成熟,智能指针回收精确,时间点掐的准,本来就是各有各的好处。

如果非要说怎么样,Python的思路不错:上层用的引用计数,这其实就是智能指针(Python的内存是即时回收的),然而,如果存在循环引用,最终垃圾回收会解决这个问题。对于非内存资源,形成循环引用的机会是很少的。实在不行,Python还有with语句。

然而,这样就增加了C API的复杂度了——C API就必须管理对象之间的引用计数加减了。Py_Inc和Py_Dec就是干这个事儿的。

那么,究竟应该怎么办呢?

对于通用语言,采取引用计数+垃圾回收的方法,在底层做这样的工作,然后在上层给予一个简单的接口,对于编译型语言,这是一个好选择。

对于关注API的嵌入式语言,直接采取垃圾回收也不失是一个好办法。

对于脚本语言,如何选择,就和实际的应用相关的。对于通用脚本语言,Python的做法不失为一个万全手段。

三十年前,大家还在满屏幕的写GOTO,直到有人发明的了控制流语句,的确有控制流语句无法完成但GOTO可以完成的任务,但是已经很少了,现在大家都很习惯if,while,for这些东西了。

三十年后,大家还在笨拙的手动管理内存,直到有人发明的垃圾回收,自然垃圾回收无法处理所有的情况,但这些情况还是会慢慢改善的,况且还有引用计数存在。我相信垃圾回收计数最终还是会成为和控制流语句一样的东西,成为大家司空见惯的本能的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值