GC就是Garbage Collection的缩写,翻译过来叫垃圾收集。所谓的垃圾,大多数情况下,指的是不再使用的内存。不收集会怎么样?会内存泄漏呗,房间就那么大,垃圾越来越多,可用空间就会越来越少。

几乎所有的编程语言,都有内存管理的办法。

只不过有的编程语言只提供了手动内存管理的方式,开发者必须自行对内存进行申请和释放。虽然手动管理相对精准,但是其实很麻烦,稍有不慎,就会造成内存泄漏、指针乱跑等各种奇奇怪怪的后果。

而类似Golang之类的编程语言,提供了垃圾收集的机制,开发者只需要了解垃圾收集的机制,大多数时间,并不需要手动管理内存。这为开发者提供了很大的便利,提升了工作效率。

但是,垃圾收集的工作机制并非完美,所以,在带来便利的同时,也会产生一些问题。总之,合理的利用垃圾收集带来的方便,避免垃圾收集影响业务,才是物尽其用。

拉几头驴,用来描述一下常见的垃圾收集机制:

1、引用计数。

谁想用驴干活的时候,就在驴身上画个圈圈,用一次画一个,用完了把代表本次使用的圈圈擦掉。当这头驴身上没圈圈的时候,就可以卸磨杀驴了,身上有圈圈的驴不能杀。

这个办法的优点是:用驴的人清楚自己用了哪头驴,一旦用驴的人忘了,当事驴自己也清楚。找不到主人,驴可以自己清除圈圈,洗白白再上路。

缺点也很明显:画圈圈也是体力活,需要时间;另一方面,驴皮就那么点面积,好几个圈圈画重叠了也是麻烦,再加上有可能圈圈套圈圈,驴也很惆怅啊。

2、标记清除。

想用某驴干活,就拍张这头驴的照片贴墙上,驴去干活就行了。驴干完活,去墙上把照片摘下来。有其他人用驴,也是拍照片贴墙上,以此类推。当墙上没有某头驴的照片时,这头驴就可以杀了吃肉了。

这个办法的优点是:驴没啥负担,只管干活;墙可以很宽,贴多少照片问题不大。

缺点是:找照片是个技术活,太笨的话,光是找照片就得很长时间。而且找照片的时候,最好别贴新的照片,不然就乱了,还得重新找。

3、分代收集。

驴多了不能放一起,短工驴放一个地方,长工驴放另一个地方。短工驴总是干活不休息,就升级成长工驴。杀驴的时候,一个人专杀干完短工的驴,短工驴不会摆资格,杀就杀了;另一个人专杀干完长工的驴,长工驴资格老,脾气暴,杀之前要点蚊香放音乐,搞点仪式感。

这个办法的优点是:非常驴性化,充分照顾了驴的感受;另一方面,多几个人杀驴,效率比较高。

缺点一样很明显:驴会升级,短工驴升级到长工驴,总是很矫情,两边杀手必须做好交接工作,不然有驴忘了杀就麻烦了。

---

Golang采用的是“标记清除”的杀驴方式,不对,是垃圾收集方式。因为这种方式关注点很简单,只要找驴照片足够快,其他都好办。

但是因为低版本的Golang,找驴照片的工作不熟练,再加上这种找驴方式本身的特点,就造成了Golang经常被人诟病的两个槽点:

1、Golang的GC速度慢。

这个在各个版本,一直是golang改进的重点。到目前为止(1.14),经过超级多版本的演进,特别是在1.14版本中实现了基于信号的真抢占式调度,目前的gc速度已经相当快了。不能跟纯手动内存管理比,只能说在同样使用gc机制的编程语言里,达到了应有的水平(之前确实有几个版本,达不到及格线)。

2、Golang的GC的STW(STOP THE WORLD)噩梦

前面提到过,找驴照片的时候,不能有新的照片贴墙上。这就造成了找驴的过程中,整个世界都停止了(其实有点夸张,毕竟只是照片墙不接受新照片,驴其实还在干活或者等死)。这个从根本上说,是机制问题。只要还用这个机制,那世界还是会时间停止。只不过,随着找驴速度的加快,这个时间裂缝,已经压缩到一个非常非常短的瞬间。当然,Golang本身的演进过程中,采用了很多细节技术,不但让这个STW的时间越来越短,而且还尽量的减少了这个停止世界的大小,把影响范围收窄。

任何GC的机制,都是有其优缺点的。

不能因为GC的弊端,就因噎废食。现在不使用GC的编程语言也有不少,比如C、比如Rust,效率确实高,也没有GC带来的问题。但还是那句话,有利就有弊,这些语言无一例外的是入门曲线很陡,内存管理需要专门花精力。

知其理、明其行、扬其长、避其短,所谓“君子生非异也,善假于物也”。

---

注:本回答只是简单说明一下Golang的垃圾收集机制。具体详细原理和代码解读,请参与专业文档。