免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 12262 | 回复: 33
打印 上一主题 下一主题

[技术动态] [转] 垃圾收集真的有用么? [复制链接]

论坛徽章:
3
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:58:11数据库技术版块每日发帖之星
日期:2015-08-30 06:20:00
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2013-09-03 02:58 |只看该作者 |倒序浏览
转自   http://avboost.com/t/gc/256


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

任何一个程序都需要内存管理. 它不管是简单的还是复杂的, 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 就是这条捷径.

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
2 [报告]
发表于 2013-09-03 08:34 |只看该作者
的确,智能指针+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这些东西了。

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





论坛徽章:
3
寅虎
日期:2013-11-27 07:53:29申猴
日期:2014-09-12 09:24:152015年迎新春徽章
日期:2015-03-04 09:48:31
3 [报告]
发表于 2013-09-03 09:01 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
5
技术图书徽章
日期:2013-08-17 07:26:49双子座
日期:2013-09-15 16:46:29双子座
日期:2013-09-25 08:17:09技术图书徽章
日期:2013-09-25 09:11:42天秤座
日期:2013-10-01 16:25:34
4 [报告]
发表于 2013-09-03 09:49 |只看该作者
满嘴大粪,

论坛徽章:
6
寅虎
日期:2013-10-10 21:59:16狮子座
日期:2013-11-12 09:24:41金牛座
日期:2013-12-14 17:02:23酉鸡
日期:2014-01-16 12:34:37技术图书徽章
日期:2014-02-15 12:52:31巨蟹座
日期:2014-05-17 14:09:52
5 [报告]
发表于 2013-09-03 10:31 |只看该作者
linus当年对c++的内存管理这块不是非常的不屑吗,难道到了c++11就有啥不同了?linus当年的意图很明确,绝大多数c++程序员都处理不好内存管理这块,谈何抽象编程,这语言是留给火星人用的?所以,要么GC,要么就象c语言哪样简单明确。

论坛徽章:
14
巨蟹座
日期:2013-11-19 14:09:4615-16赛季CBA联赛之青岛
日期:2016-07-05 12:36:0515-16赛季CBA联赛之广东
日期:2016-06-29 11:45:542015亚冠之全北现代
日期:2015-07-22 08:09:472015年辞旧岁徽章
日期:2015-03-03 16:54:15巨蟹座
日期:2014-12-29 08:22:29射手座
日期:2014-12-05 08:20:39狮子座
日期:2014-11-05 12:33:52寅虎
日期:2014-08-13 09:01:31巳蛇
日期:2014-06-16 16:29:52技术图书徽章
日期:2014-04-15 08:44:01天蝎座
日期:2014-03-11 13:06:45
6 [报告]
发表于 2013-09-03 10:43 |只看该作者
我也认为各有优劣,但不看好GC。
GC这种取巧偷机的办法,在理论上是无法完美的,虽然一下子解决了一大半的问题,但余下的问题就无法解决了。
我认为做事,得用正确的手段,即便正确的手段起始效果很慢。

内存只是资源中的一小部分,单就内存搞这些幺蛾子我觉得纯属浪费时间
对于内存这种资源而言,在C++代码中,其实很少会显式的去分配和释放,能放栈的放栈中,动态的用stl容器,余下的几个new/delete就像秃子头上的虱子。
对于Java而言,最大的问题就是,JVM或者说GC太耗内存了,经常因为内存不足而crash。讲个故事吧,当年,村头有户人家晒馒头片,少了几十片,便怀疑是邻居干的,坚持找来村干部。……中间过程略……,最终结果是从几个小孩哪儿追讨到未吃完的馒头片几片,失去的是,家里的两只鸡和一串腊肉。
再讲个同样的笑话,前几天看到的:我给女朋友的信用卡被偷了,但我一直没报警,因为小偷刷卡比我女朋友少多了。

论坛徽章:
0
7 [报告]
发表于 2013-09-03 11:04 |只看该作者
本帖最后由 zhouzhenghui 于 2013-09-03 11:06 编辑

垃圾回收和智能指针在技术本质上是一致,不同的只是代码的存放位置,可以说垃圾回收能够完成的,智能指针也一定可以完成,甚至可能完成的更好(信息更完整)。比如循环引用,boost里也有block_ptr提案,https://svn.boost.org/svn/boost/sandbox/block_ptr/。只是为了这一点优势,需要使用者理解更多的概念,增加了一点点负担。

论坛徽章:
17
处女座
日期:2013-08-27 09:59:352015亚冠之柏太阳神
日期:2015-07-30 10:16:402015亚冠之萨济拖拉机
日期:2015-07-29 18:58:182015年亚洲杯之巴勒斯坦
日期:2015-03-06 17:38:17摩羯座
日期:2014-12-11 21:31:34戌狗
日期:2014-07-20 20:57:32子鼠
日期:2014-05-15 16:25:21亥猪
日期:2014-02-11 17:32:05丑牛
日期:2014-01-20 15:45:51丑牛
日期:2013-10-22 11:12:56双子座
日期:2013-10-18 16:28:17白羊座
日期:2013-10-18 10:50:45
8 [报告]
发表于 2013-09-03 12:00 |只看该作者
回复 2# starwing83


    虽然说“循环引用”是个问题,但说COM死在这上面是不是太武断了,有没有什么论证?
    “Python的内存是即时回收的”这个说法有问题,python几乎从不真正释放它占用的内存,会留给后序对象继续使用。

论坛徽章:
17
处女座
日期:2013-08-27 09:59:352015亚冠之柏太阳神
日期:2015-07-30 10:16:402015亚冠之萨济拖拉机
日期:2015-07-29 18:58:182015年亚洲杯之巴勒斯坦
日期:2015-03-06 17:38:17摩羯座
日期:2014-12-11 21:31:34戌狗
日期:2014-07-20 20:57:32子鼠
日期:2014-05-15 16:25:21亥猪
日期:2014-02-11 17:32:05丑牛
日期:2014-01-20 15:45:51丑牛
日期:2013-10-22 11:12:56双子座
日期:2013-10-18 16:28:17白羊座
日期:2013-10-18 10:50:45
9 [报告]
发表于 2013-09-03 12:08 |只看该作者
就论楼主论证GC有问题的一面一样,GC也是在不停的发展和改进的,今天是什么样不代表明天还是。GC适合解释性语言但不适合直接生成native程序的语言,别的不说GC至少会引用巨大的运行时库或VM系统。至于C/C++,在强调底层控制以及灵活性的重要性时必然与GC是矛盾的,而且考虑到代码的兼容性以及系统交互的兼容性,引入GC会是个得不偿失的挑战。以后C/C++可能会借鉴很多GC的作用,但绝不是东风压倒西风或者西风压倒东风般的简单胜利。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
10 [报告]
发表于 2013-09-03 12:41 |只看该作者
智能指针,只是一种垃圾回收技术,并且有时还不太靠谱。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP