(转) golang 垃圾回收 gc

2019-03-15  本文已影响0人  one_zheng

(转载自)https://www.cnblogs.com/diegodu/p/5606937.html

什么是垃圾回收?

  曾几何时,内存管理是程序员开发应用的一大难题。传统的系统级编程语言(主要指C/C++)中,程序员必须对内存小心的进行管理操作,控制内存的申请及释放。稍有不慎,就可能产生内存泄露问题,这种问题不易发现并且难以定位,一直成为困扰开发者的噩梦。如何解决这个头疼的问题呢?过去一般采用两种方法:

  为了解决这个问题,后来开发出来的几乎所有新语言(java,python,php等等)都引入了语言层面的自动内存管理--也就是语言的使用者只用关注内存的申请而不必关心内存的释放,内存释放由虚拟机(virtual machine) 或运行时 (runtime) 来自动进行管理。而这种对不再使用的内存资源进行自动回收的行为就被称为垃圾回收

常见的垃圾回收方法

引用计数(reference counting)

  这是最简单的一种垃圾回收算法,和之前提到的智能指针异曲同工。对每个对象维护一个引用计数,当引用该对象的的对象被销毁或更新时该引用对象的引用计数自动减一,当被引用对象被创建或被赋值给其他对象时引用计数自动加一。当引用计数为0时立即回收对象。

  这种方法的优点是实现简单,并且内存的回收很及时。这种算法在内存比较紧张和实用性比较高的系统中使用的比较广泛,如 ios cocoa框架,php, python等。简单引用计数算法也有明显的缺点:

标记-清除(mark and sweep)

  该方法分为两步,标记从根变量开始迭代得遍历所有被引用的对象,对能够通过应用遍历访问到的对象都进行标记为"被引用";标记完成后进行清除操作,对没有标记过的内存进行回收(回收同时可能伴有碎片整理操作)。这种方法解决了引用计数的不足,但是也有比较明显的问题:每次启动垃圾回收都会暂停当前所有的正常代码执行,回收时系统响应能力大大降低!当然后续也出现了很多mark&sweep算法的变种(如三色标记法)优化了这个问题。

分代收集(generation)

  经过大量实际观察得知,在面向对象编程语言中,绝大多数对象的生命周期都非常短。分代收集的基本思想是,将堆划分为两个或多个称为 代(generation)的空间。新创建的对象存放在称为新生代(young generation)中(一般来说,新生代的大小会比老年代小很多),随着垃圾回收的重复执行,生命周期较长的对象会被提升(promotion)到老年代中。因此,新生代垃圾回收和老年代垃圾回收两种不同的垃圾回收方式应运而生,分别用于对各自空间中的对象执行垃圾回收。新生代垃圾回收的速度非常快,比老年代快几个数量级,即使新生代垃圾回收的频率更高,执行效率也仍然比老年代垃圾回收强,这是因为大多数对象的生命周期都很短,根本无需提升到老年代

Go的垃圾回收器

  go语言垃圾回收总体采用的是经典的mark and sweep算法。

实践经验

团队在实践go语言时同样碰到最多和最棘手的问题也是内存问题(其中gc为主),这里把遇到的问题和经验总结下,欢迎大家一起交流探讨。

go程序内存占用大的问题

这个问题在我们对后台服务进行压力测试时发现,我们模拟大量的用户请求访问后台服务,这时各服务模块能观察到明显的内存占用上升。但是当停止压测时,内存占用并未发生明显的下降。花了很长时间定位问题,使用gprof等各种方法,依然没有发现原因。最后发现原来这时正常的…主要的原因有两个,

一是go的垃圾回收有个触发阈值,这个阈值会随着每次内存使用变大而逐渐增大(如初始阈值是10MB则下一次就是20MB,再下一次就成为了40MB…),如果长时间没有触发gc go会主动触发一次(2min)。高峰时内存使用量上去后,除非持续申请内存,靠阈值触发gc已经基本不可能,而是要等最多2min主动gc开始才能触发gc。

第二个原因是go语言在向系统交还内存时只是告诉系统这些内存不需要使用了,可以回收;同时操作系统会采取“拖延症”策略,并不是立即回收,而是等到系统内存紧张时才会开始回收这样该程序又重新申请内存时就可以获得极快的分配速度。

gc时间长的问题

对于对用户响应事件有要求的后端程序,golang gc时的stop the world兼职是噩梦。根据上文的介绍,1.5版本的go再完成上述改进后应该gc性能会提升不少,但是所有的垃圾回收型语言都难免在gc时面临性能下降,对此我们对于应该尽量避免频繁创建临时堆对象(如&abc{}, new, make等)以减少垃圾收集时的扫描时间,对于需要频繁使用的临时对象考虑直接通过数组缓存进行重用;很多人采用cgo的方法自己管理内存而绕开垃圾收集,这种方法除非迫不得已个人是不推荐的(容易造成不可预知的问题),当然迫不得已的情况下还是可以考虑的,这招带来的效果还是很明显的~

goroutine泄露的问题

我们的一个服务需要处理很多长连接请求,实现时,对于每个长连接请求各开了一个读取和写入协程,全部采用endless for loop不停地处理收发数据。当连接被远端关闭后,如果不对这两个协程做处理,他们依然会一直运行,并且占用的channel也不会被释放…这里就必须十分注意,在不使用协程后一定要把他依赖的channel close并通过再协程中判断channel是否关闭以保证其退出。

上一篇 下一篇

猜你喜欢

热点阅读