标记阶段:在堆里激活存放着所有对象实例,在GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已死亡的对象,GC才会在执行垃圾回收时释放掉其所占用的内存空间,这个过程称之为垃圾标记阶段
标记阶段:引用技术算法
对每一个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况
对于一个对象A,只要有任何一个对象引用A,则A的引用计数器就+1;当引用失效时计数器就-1。只要对象A的引用计数器的值为0,即表示对象A不能再被使用则可进行回收。
优点:实现简单,垃圾对象便于辨识 判定效率高,回收无延迟性
缺点:
-
需要单独的字段存储计数器增加存储空间的开销
-
每次赋值都需要更新计数器,增加了时间开销
-
无法处理循环引用的情况,致命缺陷导致Java回收器中没有使用这类算法
Python使用的引用计数法,使用手动接触和弱引用weakref解决循环引用问题
标记阶段:可达性分析算法(根搜索算法)
基本思路:
-
可达性分析算法是以根对象集合为起始点按照从上到下的方式搜索被根对象集合所连接的目标对象是否可达
-
使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接的连接着,搜索所走过的路径称为引用链
-
如果目标对象没有任何引用链相连,则是不可达的,意味着该对象已经死亡
-
在可达性分析算法中只有能够被根对象直接或间接链接的对象才是存活对象
GC Roots:
-
虚拟机栈中引用的对象
-
本地方法栈内本地引用对象
-
方法区中静态属性引用的对象,常量引用的对象
-
被同步锁所持有的对象
若一个指针保存了堆内存里的对象,当时又不存放在堆内存里那它就是一个Root
对象的finalization机制
Java语言提供了对象终止机制来允许开发人员提供对象被销毁之前的自定义处理逻辑,在垃圾回收 此对象之前总会先调用这个对象的finalize()方法。
finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放
用于不要主动去调某个对象的finalize()方法,应该交给垃圾回收机制调用
finalize()只会被调用一次,finalize()是对象逃脱死亡的最后机会
虚拟机中对象三种状态:
-
可触及
-
可复活的:对象的所有引用都被释放,但是对象有可能在finalize()中复活
-
不可触及的:对象finalize()被调用,并且没有复活则会进入不可触及状态
清除阶段:标记-清除算法
执行过程:当堆中有效内存空间被耗尽时,就会停止整个程序,然后进行标记-清除
-
标记:Collector从引用根节点开始遍历,标记所有被引用的对象,一般是在对象的Header中记录为可达对象
-
清除:Collector对堆内内存从头到尾进行线性遍历,如果发现某个对象在其header中未被标记为可达对象则将其回收
缺点:效率不高,进行GC是需要停止整个应用程序。清理出来的空间是不连续产生内存碎片
清除阶段:复制算法
在新生代的幸存者区域用的就是这个算法
优点:无标记清除过程运行效率高,复制后保证空间的连续
缺点:需要两倍的内存空间
系统中的垃圾对象若很多,存活率低,复杂算法需要复制的存活对象不需要太大才适合使用
清除阶段:标记-压缩(整理)算法
老年代中对象存活率很高,复制算法就不适用了,标记-清除算法可用于老年代,但是执行效率低还容易产生随便,所以在此基础上标记-压缩算法应运而生
执行过程:
-
第一阶段和标记-清除算法一样,从根节点开始标记所有被引用的对象
-
第二阶段将所有的存活对象压缩到内存的一端按顺序排放
-
清理边界所有的空间
优点:清理后的内存连续,消除了复制算法中内存减半的高额代价
缺点:效率不高,移动对象的同时若对象被其他对象引用则还需要调整引用地址,移动过程需要全程暂停用户的应用程序
小结
MARK-SWEEP | MARK-COMPACT | COPYING | |
---|---|---|---|
速度 | 中等 | 最慢 | 最快 |
空间开销 | 少(有碎片 | 少(无碎片 | 两倍的内存空间(无碎片 |
移动对象 | 否 | 是 | 是 |
根据不同的算法具体问题具体分析,根据不同的生命周期采用不同的垃圾回收方法
年轻代:区域较老年代较小,对象生命周期短,存活率低,回收频繁,所以使用复制算法更使用
老年代:区域大,对象生命周期长,存活率高,回收不及年轻代频繁,一般使用标记-清除,标记-压缩混合实现