在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
Go语言的实时GC原理和实践每天,Pusher(原作者的一个程序)将数十亿的信息实时地(准确地说是从发送方到达接收方所需时间在100毫秒以下),其重要原因是Go语言的低延迟垃圾回收实现。
从Haskell到Go现在的Pusher原来是用Haskell语言写的,后来才用Go重写,根本原因在于GHC有根本性的延迟问题。更具体地说,是因为GHC的垃圾回收器是根据Working set(内存内的对象)到达某一个比例后来进行一次会导致全局中止的垃圾回收处理。这样当内存中存在大量对象时,会每隔数百毫秒就进行一次垃圾回收,而且是没有意义的垃圾回收(可能只有极少数的未被引用的对象)。
并行垃圾回收的运行原理到底Go是如何实现GC的并行处理的呢?其核心在于三色标记和扫除算法,一下的图片将展示此算法的运行机制,请重点留意它是如何实现并行处理的。
时期1:程序运行改程序对多个链表进行操作,一开始共有A、B、C三个节点对象,红色的对象A和B是根对象,通常来说都是可达的。垃圾回收器将对象分为黑、灰、白三个集合,因为现在GC周期还没开始,所以他们都属于白色集合。
时期2:程序运行新增一个对象D,作为A的next节点,因为一般GC线程初始化之后新增的对象都会被分到灰色集合中,D也不例外。
时期3:GC扫描GC周期开始时,根对象都将移动到灰色集合中,此时灰色集合中有A、B、D三个对象。注意,此时正常程序并没有因此而停止。
时期4:GC扫描为了实行扫描,GC首先选择根对象A,把它移动到黑色集合并把它所引用的子对象移动到灰色集合,此时A只引用了D,而D本来就属于灰色集合,因此并不需要移动。无论进行到哪个阶段,GC都可以计算出剩余的对象移动次数=2*|white|+|grey|,把全部阶段都完成至少需要一次的移动,以使得剩余的对象移动次数=0。
时期5:程序运行新对象E生成,并作为C的next节点,正如时期2所说的,它被分配到灰色集合。程序也因此而增加了所需的GC阶段数,导致最终的扫除阶段被延迟。
时期6:程序运行此时B把next指针指向了对象E,从而使对象C变成不可达对象。也就是说,对象C将残留在白色集合中,这个集合正是最终的扫除阶段会被回收内存空间的。
时期7:GC扫描扫描继续进行,GC这次选择了对象D,但D并没有下层对象,即本次无可移动到灰色集合的对象。
时期8:程序运行此时B又把next指针设置为空,对象E也变得不可达。嗯,正如你所想的,E位于灰色集合中,并不能被回收,是不是会有内存泄露的风险啊?但这实际上并不是问题,E将在下一次GC周期被回收。三色标记和扫除算法能保证在GC周期开始时不可达的对象将会在周期结束时被回收。
时期9:GC扫描GC这次选择了对象E进行扫描,因此E将被移动到黑色集合,但E并没有下层对象,注意这里对象C将永远不会移动到其他集合,因为他是E的上层对象而不是下层对象。
时期10:GC扫描GC在最后将选择灰色集合中的对象B进行扫描,此时灰色集合将变为空。
时期11:GC清除GC将回收白色集合中的对象(垃圾)的内存空间,它们是绝对的不可达对象,可以放心地杀死。而对象E是在该GC周期内突然变得不可达的,将留在下一个GC周期才被清除。
时期12:GC重置在实际运用中,没有必要把所有黑色集合中的存留对象再移动会白色集合,只需将黑色集合重新解释为白色集合,白色集合重新解释为黑色集合即可,既简单又快速。
两个全局停止时期第一个是为了确定根对象的栈空间扫描,第二个是GC扫描的最终清除阶段。好消息是,第二个全局停止时期在最近的版本中已经被优化了,完全可以避免。然而这两个全局停止时期即使对于一个很大的堆也不用1毫秒即可完成。
Latency(延迟) vs. Throughput(吞吐量)即使利用并行GC对于很大的堆也能提供大规模的低延迟服务,那为什么还有很多人选择全局停止的垃圾回收器(比如Haskell的GHC)呢?Go的并行垃圾回收器与GHC的全局停止GC估计只好那么一点点吧? |
请发表评论