这里说说5种GC算法,在说GC算法前,先谈谈判断对象是否活着的可达性分析算法
垃圾回收,首先要判断对象是否还活着.
Java通過可达性分析(Reachability Analysis)来判定对象是否存活
算法基本思路:
- 通过一系列称为”GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路径,叫做引用链(Reference Chain)
- 当一个对象到GC Roots没有任何引用链相连,从图论来说,就是从GC Roots到对象不可达,此时判定对象不可用
Java中,可作为GC Roots的對象包括四种
创建–>可达<–>可恢复–>不可达–>垃圾回收
创建对象,并把对象赋给一个引用变量,程序通过这个引用变量来操作对象,对象和数组都采用了强引用。
一个对象被一个或以上引用变量引用,则处于可达状态,不会被GC
即使在可达性分析算法中不可达的对象,也并非是“非死不可”,这时还处于“缓刑”状态,真正标记一个对象的死亡,至少经历两次标记过程
老牌的垃圾回收算法
给对象添加一个引用计数器,有一个地方引用它时,计数器+1;引用失效时,-1;计数器为0,对象就是不可能再被使用
客观来说,实现简单,判定效率高
应用如:微软的Component Object Model技术,Python,ActionScript3
但主流Java虚拟机没有用,因为很难解決对象间的循环引用问题
这涉及到如何判定对象是否可达,或者说是否活着,被引用着
接着,标记完成后,统一回收所有标记的对象
主要问题:
为解决效率问题,复制算法出现了
将内存分为大小相等的两块,每次只用一块
根据老年代的特点,提出了标记-整理/压缩算法
标记阶段与标记-清除算法一样
当前的商业虚拟机的垃圾回收都采用 分代收集 算法。根据对象存活周期的不同,将内存划分为几块。
一般将Java堆分为新生代和老年代。
新生代: 每次GC都有大批对象死去,只有少量存活
老年代:对此存活率高,没有额外空间做分配担保
首先,要解决的,是可达性分析,从GC Roots中找对象是否有可达的引用链。
可做GC Roots节点的,上文说过,主要有 全局性的引用(包括常量或类静态属性),执行上下文(栈帧中的本地变量表和JNI引用的对象). 问题是,现在的应用,仅方法区就数百Megabyte,如果逐个检查这里边的引用,必然消耗很多时间
可达性分析必须在一个能确保一致性的内存快照中进行,分析期间,内存系统不能出现变化,否则,会出现分析不正确的情况,例如,前脚这边标记了A对象不可达,此时内存系统还在运行,万一A又被引用了,但A已经被标记为不可达。所以,得有GC停顿,号称几乎不会GC停顿的CMS收集器,在枚举根节点时,也必须停顿
目前主流VM使用准确式GC,所以不需要一个不漏地检查完所有的全局和上下文的引用的位置,虚拟机有办法直接得知哪些地方存放着对象的引用
HotSpot的实现中,使用一组叫做OopMap的数据结构来达到目的。
(1)类加载完成时,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来。
(2)在JIT编译过程中,会在特定的位置记录下栈和寄存器中,哪些位置是引用
这样,GC扫描时,就可以直接得知这些信息了
在OopMap(Ordinary Object Pointer Map)的协助下,HotSpot可以快速准确完成GC Roots枚举。
问题是,引用关系变化,OopMap内容变化的指令非常多,如果为每一条指令生成对应的OopMap,那会需要有大量的空间,GC的空间成本会变得很高
事实上,HotSpot没有为每条指令生成OopMap,前面说了,是在“特定位置”,这个特定位置,就是安全点SafePoint. 程序执行不是所有地方都停顿下来GC,而是到安全点,才停顿。
安全点选定
所以,安全点的选定,不能太过少,否则GC等待时间太长,产生OOM;也不能太过频繁,否则会增大运行时的负荷。
因此,安全点选择的准则:
具有这个特征的就是指令序列复用,例如方法调用,循环跳转,异常跳转。所以,具有这些功能的指令才会产生SafePoint
让所有线程跑到最近的安全点
另一个问题,发生GC时,如何让所有线程跑到最近的安全点,除了JNI调用的线程。
两种方案
SafePoint只是解决获得CPU执行时间的线程,在不太长的时间内,可以遇到GC的SafePoint的问题。
但没有分配到CPU执行时间的线程,如Sleep,Block状的线程,它们无法响应JVM的中断请求,跑到SafePoint中断挂起,JVM显然也不会等待线程重新获得CPU时间
对这一问题,需要安全区域Safe Region
安全区域
当线程执行到安全区域中的代码时,首先标识自己已经进入Safe Region。那么,这段时间里JVM发起GC时,就不用管标识自己在Safe Region状态的线程了,因为这些线程的引用关系不会发生变化。
线程离开Safe Region时,要检查系统是否已经完成GC Roots枚举或者整个GC过程。如果沒有,就必須等待,直到收到可以安全离开的信号为止;如果完成了,线程继续执行。