带有运行时且常驻内存的编程语言才需要用到垃圾回收功能。

gc 各种编程语言实现都不一样,性能也不一样,但是工作流程却是一致的。基本流程为:标记、清理、整理。

标记也就是找出活着的对象与死去的对象。标记的算法也基本一致:从栈中还有其他区域找出存活对象的引用,引用到的对象就是存活的,再依次递归找出引用的引用,这个算法有时候被称为三色算法。

从栈中,还有静态变量中查询出所有对象。依次在堆中查询这些对象,和对象持有的引用并对这些对象染色。先染色对象本身,然后在循环对象持有的字段,遇到字段指向的对象已经染色就会跳过,继续下一个,直到完成所有染色。

下一个阶段就是清理,如果直接清理掉对象,会产生大量的内存碎片,为了解决内存碎片的问题,会对堆进行分区,根据对象生命周期的长短分配到不同堆分区里面,减少内存碎片需要移动对象,最后还需要更新引用。

full GC 会带来 STW 问题,就是整个进程完全停止工作,锁定所有线程,只执行清理对象,当然并不是整个 full GC 过程都处于锁定状态,虽然时间很短,但是在高并发场景还是很要命。jvm 优化通常优化 GC 参数,减少 GC 带来的影响。当然加钱上集群是一种高效解决 jvm GC 导致停机的好方法。

openJDK 附带的 vm 还有 Chrome v8 引擎都是采用这一类策略。Go 运行时的 gc 策略有点差异,对于局部对象,在编译阶段做逃逸分析,如果可以用完即销毁,那么就不会在堆上分配内存。Go 运行时在分配内存的时候采用 TCMalloc 的算法,在分配内存阶段就尽可能的较少碎片的出现,所以堆也没有进行分区,或者说在分配内存的时候,已经为了减少碎片分区好了。

一方面运行时的 GC 服务会周期性的回收毁弃对象,在写代码的时候,可以选择性的重复使用一些对象,减少 gc 负担。Golang 的 sync.Pool 就是一个用来优化的代码的结构体,手动管理对象使用和释放。fasthttp 就是一个号称运行速度比 net/http快十倍的 基础 http 框架,就是大量采用 sync.Pool ,将使用完成后的对象缓存着,不让 GC 回收,这样就可以减少创建和销毁对象引起的资源浪费。带来的问题就是请求对象,在请求生命周期结束之后继续使用会带来未定义行为。