更新时间:2021-10-22 来源:黑马程序员 浏览量:
添加QQ(注意~~添加好友界面,选择找人):435946716,【免费】获取《JVM核心教程:JVM从门到精通_JVM虚拟机底层原理深入教程》全套视频教程+配套资料。
在堆里面存放着各种各类的Java对象,垃圾收集器在对堆进行垃圾回收时,首要就是判断哪些对象还活着,哪些对象已经死去(即不被任何途径引用的对象)。
引用计数器算法:
引用计数器算法简单概括为:给对象添加一个引用计数器,每当有一个地方引用该对象时,计数器+1,当引用失效时,计数器-1,任何时刻,当计数器为0的时候,该对象不再被引用。客观的说,引用计数器的实现简单,判定效率也高,大部分场景下是一个不错的选择。但是,当前主流的Jvm均没有采用标记清除算法,原因在于,它很难解决对象之间互相循环调用的情况。
可达性分析算法:
在主流的商用程序语言(如C#, Java)的主流实现中,都是通过可达性分析来判断对象是否存活,这个算法的思想就是通过一系列的成为"GC
Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到"GC
Roots"没有任何引用链相连,则证明此对象是不可用的。
如图所示,虽然Obj5, Obj6, Obj7互有关联,但是他们到GC root没有任何引用链,所以判定为需要被回收的对象。
常说的GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC roots且没有被GC roots引用的对象。
在Java中,可以作为GC Roots的对象包括下面几种:
·虚拟机栈中引用的对象;
·方法区中类静态属性引用的对象;
·方法区中的常量引用的对象;
·本地方法栈中JNI(即一般说的Native方法)的引用的对象;
再谈引用
无论是通过引用计数器判断的引用数量,还是通过可达性分析判断出的引用链是否可达,判定对象是否存活都跟引用有关。在JDK1.2以前,引用被定义为当一个reference类型的数据代表的是另外一块内存的起始地址,该类型的数据被称之为引用,这种定义很纯粹,但是也很狭隘,一个对象在这种定义下只有被引用和没有被引用两种状态。对于描述一些“食之无味,弃之可惜”的对象就显得无能为力。我们希望能描述这类对象,当内存足够的时候,将它存放在内存中,当内存空间进行垃圾回收后显得还是内存紧张时,可以回收这部分对象,很多系统的缓存功能都符合这样的应用场景。因此在JDK1.2以后对引用进行重新的扩充,分为强引用,软引用,弱引用,虚引用4中,这四种引用的强度依次递减。
强引用:
强引用是在代码中普遍存在的,类似于Object obj = new Object(),只要强引用一直存在,垃圾收集器就永远不会回收被引用的对象。
软引用:
软引用用来描述一些还有用但并非必须的对象,对于软引用关联着的对象,当内存溢出异常发生之前,通过垃圾回收进行二次回收。如果二次回收完成之后,系统内存依然不够,才会抛出内存溢出异常,在jdk1,2以后用SoftReference类来实现软引用。
弱引用:
弱引用也是用来描述非必须对象的,但是它的强度相比于软引用来说更弱一些,它仅仅能生存到下一次垃圾回收之前。当垃圾收集时,无论内存是否足够,弱引用的对象都要被回收,在jdk1.2以后用WeakReference类来实现弱引用
虚引用:
虚引用是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过一个虚引用来获取一个实例对象。为一个对象设置弱引用的唯一目的就是该对象在垃圾回收时受到一个系统通知,Jdk1,2以后用PhantomReference实现虚引用。
生存还是死亡?
即使在可达性分析中,没有引用链到达GC Roots,也并非是“非死不可”的。这个时候对象处于缓刑阶段,要正式宣告死亡,至少要经历两次标记的过程。如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并进行一次筛选,筛选的条件是此对象是否要执行finalize()方法,当对象么有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为不必要执行finalize()方法。
如果该对象被判定要执行finalize()方法,那么这个对象会被放在一个叫F-Queue的队列中,并在稍后有一个虚拟机自行创建的,优先级较低的线程去执行它,这里的执行是指会触发finalize()方法,但并不会等待它执行结束。这样做的原因是如果一个对象在执行finalize()时非常缓慢,或者执行了死循环,这样就会导致F-Queue中的其他对象处于等待中,严重的会导致整个垃圾回收系统崩溃。finalize()是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行二次标记,如果对象要在finalize()中拯救自己的话,只能重新与引用链上的任意一个对象建立关联即可,比如把对象自己(this关键字)赋值给其他成员变量或者对象,那在第二次标记时就被移出即将回收的集合,如果没有关联上,基本可以确定要被回收了。
猜你喜欢