golang垃圾回收
在以前,内存管理是程序员开发应用的一大难题
传统的系统级变成语言(比如C,C++),
程序员必须对内存小心的进行管理操作
控制内存的申请和释放
稍有不慎
就可能产生内存泄漏的问题
这种问题不容易发现
而且难以定位
解决这种问题
一般有两种办法
1.内存泄漏检测工具
这种工具的原理一般是静态代码扫描
通过扫描程序来检测可能出现内存泄漏的代码段
但是检测工具难免有疏漏和不足,只能起到辅助作用
2.智能指针
这是c++中引入的自动内存管理方法,
通过拥有自动内存管理功能的指针对象来引用对象
这样程序员不用太关注内存的释放,内存自动释放
为了解决这个问题
几乎所有的新语言,比如java,python,php等等
都引入了语言层面的自动内存管理
也就是说程序员只用关注内存的申请
而不用关注内存的释放
内存释放由虚拟机vitual machine
或者运行时runtime来自动进行清理
这种对不再使用的内存进行自动回收的行为
就被称为
垃圾回收
常见的垃圾回收方法
1.引用计数
这是最简单的一种垃圾回收算法
和之前提到的智能指针异曲同工
对每个对象维护一个引用计数
当引用该对象的对象被销毁或者更新的时候
被引用对象的引用计数就自动减一
当被引用对象被创建或者被赋值给其他对象的时候就自动加一
当引用计数为0的时候,
就自动回收对象
这种方法的优点是实现简单,内存的回收很及时
这种算法在内存比较紧张和实时性比较高的系统中使用比较广泛
比如ios cocoa框架,php,python等
缺点是
频繁更新计数降低了性能
循环引用问题
2.标记-清除
这个方法分为两步,
标记从跟变量开始迭代遍历所有被引用的对象
对能够通过应用遍历访问到的对象都标记为"被引用"
标记完成后就进行清理工作
缺点是:
每次启动垃圾回收,都会暂停当前所有的正常代码执行
系统的响应能力大大降低
3.分代收集
经过大量实际观察得知
在面向对象编程语言中,绝大多数对象的生命周期都非常短
分代收集的思想是
将堆划分为两个,或者多个称为
代generation的空间
新创建的对象存放在称为新生代young generation中,
随着垃圾回收的重复执行
生命周期比较长的对象会被提升到老年代中
golang的垃圾回收器
golang垃圾回收总体采用的是mark and sweep算法
也就是 标记-清理 算法
1.3版本以前,golang的垃圾回收算法很简陋
go runtime会在一定条件下,暂停所有任务
然后进行mark&sweep
1.3版本的时候,go runtime分离了mark和sweep
但是还是要暂停任务执行,然后启动mark
然后启动任务,然后sweep和其他任务并行执行
1.4版本的时候,runtime中很多c语言改成了go语言实现
在标记的时候,只遍历指针指向的对象
1.6版本的时候,mark操作是渐进执行的,
而不是扫描整个内存空间,
实践经验
1.go程序内存占用大的问题
go的垃圾回收有个触发阈值,
这个阈值会随着每次内存使用变大而逐渐增大
如果长时间没有触发gc
go会自动触发一次,2min
所以内存阈值上升之后,2min才触发gc
回收的时候只是告诉系统可以回收
并不是立即回收
2.gc时间长
gc时性能会下降,很多人会采用cgo来管理内存,绕开垃圾回收
但是cgo会造成一些不可预知的问题
cgo最好是作为备用方案
3.goroutine泄漏
不使用协程后,
一定要把他依赖的channel给关闭掉
并且通过在协程中去判断channel是否关闭