一、GC的回收算法:
①引用计数法:给对象添加一个引用计数器,当有一个地方引用他的时候,引用计数器就加1,当引用失效的时候,引用数就减1, 当引用计数器任何时刻都为零的的对象就是不会再被引用的对象
②可达性分析算法:算法的基本思路就是通过一系列 成为GCRoots的对象那个作为根节点,从这些节点开始向下搜索,搜索的路径成为引用链,当一个对象到GCR哦哦图书没有任何引用链的时候,则认为此对象是不可用的;
③即使可达性分析算法判定为不可达的对象,也不是非死不可的,这时候是一个缓刑阶段,至少要经历两次标记的过程,如果可达性分析判定其没有与GCRoots相连的引用链的时候,则进行第一次标记并进行筛选,筛选的条件是此对象有没有必要执行finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都判定为“没有必要执行”;如果这个finalize()方法被判定为有必要执行 finalize()方法,那么这个对象将会被方最一个F-Queue的队列中,并在稍后由一个虚拟机自动创建的、低优先级的Finalizer县城去执行它;
④finalize()方法是对象逃脱死亡命运的最后一次机会,如果对象在finalize()方法中重新与引用链上的任何一个对象建立关联,则成功自救,否则这个对象基本上就真的被回收了;
finalize()的测试方法如下:
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive(){
System.out.println("yes,i am alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
//对象的第一次自救
SAVE_HOOK = null;
System.gc();
//因为finalize()的优先级很低 ,所以暂停1秒用来等待它
Thread.sleep(1000);
if (SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead");
}
//与对象的第一次自救的代码完全相同,却失败了
SAVE_HOOK = null;
System.gc();
Thread.sleep(1000);
if (SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead");
}
}
/**
* 执行结果第一成功,第二次失败,因为任何一个对象的finalize()方法都只会被系统调用一次,如果对象面临下一次回收,他的finalize()
* 方法不会再被执行,因此第二次失败
*/
}
运行结果为:
finalize method executed
yes,i am alive
no,i am dead
Process finished with exit code 0
虽然finalize()这个方法已经很悲情了,但是这个方法并不是一个好的方法,我们要尽量的避免使用它,因为他的运行代价高,不确定性大,无法保证各个对象的调用顺序,有些教材中描述他为“关闭外部资源”之类的工作,这完全是对这个方法的一种自我安慰,finalize()能做的所有工作,使用try-finally或者其他方式都可以做的更好更及时,所以我们可以忘了Java中拥有这样一个方法;
二、回收方法区
很多人认为方法区(或者Hotpot虚拟机中的永久代)是没有垃圾收集的,并且Java虚拟机规范中说过可以不要求虚拟机在方法区进行垃圾收集,并且在方法区中进行垃圾收集的性价比一般非常低;
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类
①回收废弃常量:
与回收Java堆中的对象十分类似;一常量池中字面量的回收为例,假如一个字符串“ABC”已经进入了常量池,但是当前系统中没有任何一个String对象是这个的,如果这时发生垃圾回收,而且必要的话,这个字符串常量会被系统清理出常量池,常量池中的其他类(接口)、方法、字段的符号引用也与此类似;
②回收无用类
:该类在所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
:加载该类的ClassLoader已经被回收
:该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射来访问该类的方法
满足以上三个条件后,并不是必然会被回收是否被回收还要被虚拟机通过一些参数进行控制
三、垃圾收集算法
1、标记-清除法
这是最基础的算法,分为标记和清除两个阶段:首先标记处所有需要回收的对象,在标记完成后同意清除这些被标记的对象;
这个方法主要有两个不足:一个是清除效率过于低下,这两个过程的效率都不高;另外一个就是空间问题了,标记清除过后会产生大量的内存碎片,肯能导致以后在程序运行过程中需要分配较大对象的时候,无法找到足够的连续内存而 不得不提前出发另一次垃圾收集动作;
2、复制算法
为了解决效率问题,它将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块。当一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已经使用过内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配的时候也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效;
因为将内存缩小为了原来的一半,这样浪费太高了,因为新生代中的对象98%都是朝生夕死的,所以并不需要按照一比一的比例来华反内存空间,而是将内存分为一个Eden空间,两个Survivor空间,每次使用Eden空间和其中的一块Survivor空间。当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后才清理掉Eden空间和刚才使用过的Survivor空间。HotSpot虚拟机默认的Eden和Survivor的比例是八比一,也就是每次只有10%的会被浪费,但是98%只是一般情况下,当特殊情况的时候,当Survivor空间不够用的时候,需要依赖其他没存(这里指老年代)进行分配担保;这些多出来的对象将直接通过分配担保机制进入老年代;
3、标记-整理算法
复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用过的内存中的对象都100%存活的极端情况,所以老年代中不能直接选用这种算法;
标记-整理算法也是分为两个阶段:第一阶段与标记-清除算法的第一阶段一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存;
4、分代收集算法
目前的商业虚拟机的垃圾收集都采用“分代收集”算法,该算法就是根据对象存活周期不同将内存划分为几个块,一般把Java堆区分为新生代和老年代,然后根据每一代的特点采用最适当的收集算法;新生代采用复制算法,老年代因为存活率较高、没有额外空间对其进行担保,于是采用标记-整理或标记-清除算法
网友评论