JAVA垃圾回收-可达性分析算法

更新时间:2023-07-12 22:19:56 阅读: 评论:0

刘有林JAVA垃圾回收-可达性分析算法
在java中是通过引⽤来和对象进⾏关联的,也就是说如果要操作对象,必须通过引⽤来进⾏。那么很显然⼀个简单的办法就是通过引⽤计数来判断⼀个对象是否可以被回收。不失⼀般性,如果⼀个对象没有任何引⽤与之关联,则说明该对象基本不太可能在其他地⽅被使⽤到,那么这个对象就成为可被回收的对象了。这种⽅式成为引⽤计数法。
这种⽅式的特点是实现简单,⽽且效率较⾼,但是它⽆法解决循环引⽤的问题,因此在Java中并没有采⽤这种⽅式(Python采⽤的是引⽤计数法)。看下⾯这段代码:
聂耳简介
public class Main {
public static void main(String[] args) {
MyObject object1 = new MyObject();
MyObject object2 = new MyObject();
object1.object = object2;射手摩羯
object2.object = object1;
object1 = null;
object2 = null;
}
}
class MyObject{
public Object object = null;
}
最后⾯两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引⽤对⽅,导致它们的引⽤计数都不为0,那么垃圾收集器就永远不会回收它们。
为了解决这个问题,在Java中采取了 可达性分析法。该⽅法的基本思想是通过⼀系列的“GC Roots”对象作为起点进⾏搜索,如果
李世通在“GC Roots”和⼀个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不⼀定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须⾄少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。最后⾯两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引⽤对⽅,导致它们的引⽤计数都不为0,那么垃圾收集器就永远不会回收它们。
Java并不采⽤引⽤计数法来判断对象是否已“死”,⽽采⽤“可达性分析”来判断对象是否存活(同样采⽤此法的还有C#、Lisp-最早的⼀门采⽤动态内存分配的语⾔)。
此算法的核⼼思想:通过⼀系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索⾛过的路径称为“引⽤链”,当⼀个对象到 GC Roots 没有任何的引⽤链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可⽤。以下图为例:微信男生头像
对象Object5 —Object7之间虽然彼此还有联系,但是它们到 GC Roots 是不可达的,因此它们会被判定为可回收对象。
在Java语⾔中,可作为GC Roots的对象包含以下⼏种:
1. 虚拟机栈(栈帧中的本地变量表)中引⽤的对象。(可以理解为:引⽤栈帧中的本地变量表的所有对象)
2. ⽅法区中静态属性引⽤的对象(可以理解为:引⽤⽅法区该静态属性的所有对象)
3. ⽅法区中常量引⽤的对象(可以理解为:引⽤⽅法区中常量的所有对象)
4. 本地⽅法栈中(Native⽅法)引⽤的对象(可以理解为:引⽤Native⽅法的所有对象)
可以理解为:
(1)⾸先第⼀种是虚拟机栈中的引⽤的对象,我们在程序中正常创建⼀个对象,对象会在堆上开辟⼀块空间,同时会将这块空间的地址作为引⽤保存到虚拟机栈中,如果对象⽣命周期结束了,那么引⽤就会从虚拟机栈中出栈,因此如果在虚拟机栈中有引⽤,就说明这个对象还是有⽤的,这种情况是最常见的。
(2)第⼆种是我们在类中定义了全局的静态的对象,也就是使⽤了static关键字,由于虚拟机栈是线程私有的,所以这种对象的引⽤会保存在共有的⽅法区中,显然将⽅法区中的静态引⽤作为GC Roots是必须的。
(3)第三种便是常量引⽤,就是使⽤了static final关键字,由于这种引⽤初始化之后不会修改,所以⽅法区常量池⾥的引⽤的对象也应该作为GC Roots。最后⼀种是在使⽤JNI技术时,有时候单纯的Java代码并不能满⾜我们的需求,我们可能需要在Java中调⽤C或C++的代码,因此会使⽤native⽅法,J
VM内存中专门有⼀块本地⽅法栈,⽤来保存这些对象的引⽤,所以本地⽅法栈中引⽤的对象也会被作为GC Roots。
JVM之判断对象是否存活(引⽤计数算法、可达性分析算法,最终判定)
finalize()⽅法最终判定对象是否存活:
即使在可达性分析算法中不可达的对象,也并⾮是“⾮死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告⼀个对象死亡,⾄少要经历再次标记过程。
标记的前提是对象在进⾏可达性分析后发现没有与GC Roots相连接的引⽤链。
1).第⼀次标记并进⾏⼀次筛选。
筛选的条件是此对象是否有必要执⾏finalize()⽅法。
当对象没有覆盖finalize⽅法,或者finzlize⽅法已经被虚拟机调⽤过,虚拟机将这两种情况都视为“没有必要执⾏”,对象被回收。
2).第⼆次标记
如果这个对象被判定为有必要执⾏finalize()⽅法,那么这个对象将会被放置在⼀个名为:F-Queue的队列之中,并在稍后由⼀条虚拟机⾃动建⽴的、低优先级的Finalizer线程去执⾏。这⾥所谓的“执⾏”是指虚拟机会触发这个⽅法,但并不承诺会等待它运⾏结束。这样做的原因是,如果⼀个对象finalize()⽅法中执⾏缓慢,或者发⽣死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚⾄导致整个内存回收系统崩溃。
Finalize()⽅法是对象脱逃死亡命运的最后⼀次机会,稍后GC将对F-Queue中的对象进⾏第⼆次⼩规模标记,如果对象要在
finalize()中成功拯救⾃⼰----只要重新与引⽤链上的任何的⼀个对象建⽴关联即可,譬如把⾃⼰赋值给某个类变量或对象的成员变量,那在第⼆次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。
流程图如下:
在JDK1.2以前,Java中引⽤的定义很传统: 如果引⽤类型的数据中存储的数值代表的是另⼀块内存的起始地址,就称这块内存代表着⼀个引⽤。这种定义有些狭隘,⼀个对象在这种定义下只有被引⽤或者没有被引⽤两种状态。
我们希望能描述这⼀类对象: 当内存空间还⾜够时,则能保存在内存中;如果内存空间在进⾏垃圾回收后还是⾮常紧张,则可以抛弃这些对象。很多系统中的缓存对象都符合这样的场景。
在JDK1.2之后,Java对引⽤的概念做了扩充,将引⽤分为强引⽤(Strong Reference)、软引⽤(Soft Reference)、弱引⽤(Weak Reference)和虚引⽤(Phantom Reference)四种,这四种引⽤的强度依次递减。
傅斯年⑴强引⽤(StrongReference)台湾作家有哪些
强引⽤是使⽤最普遍的引⽤。如果⼀个对象具有强引⽤,那垃圾回收器绝不会回收它。当内存空间不⾜,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终⽌,也不会靠随意回收具有强引⽤的对象来解决内存不⾜的问题。  ps:强引⽤其实也就是我们平时A a = new A()这个意思。
⑵软引⽤(SoftReference)
如果⼀个对象只具有软引⽤,则内存空间⾜够,垃圾回收器就不会回收它;如果内存空间不⾜了,就
会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使⽤。软引⽤可⽤来实现内存敏感的⾼速缓存(下⽂给出⽰例)。
软引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果软引⽤所引⽤的对象被垃圾回收器回收,Java虚拟机就会把这个软引⽤加⼊到与之关联的引⽤队列中。
⑶弱引⽤(WeakReference)
弱引⽤与软引⽤的区别在于:只具有弱引⽤的对象拥有更短暂的⽣命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,⼀旦发现了只具有弱引⽤的对象,不管当前内存空间⾜够与否,都会回收它的内存。不过,由于垃圾回收器是⼀个优先级很低的线程,因此不⼀定会很快发现那些只具有弱引⽤的对象。
弱引⽤可以和⼀个引⽤队列(ReferenceQueue)联合使⽤,如果弱引⽤所引⽤的对象被垃圾回收,Java虚拟机就会把这个弱引⽤加⼊到与之关联的引⽤队列中。
⑷虚引⽤(PhantomReference)
“虚引⽤”顾名思义,就是形同虚设,与其他⼏种引⽤都不同,虚引⽤并不会决定对象的⽣命周期。如果⼀个对象仅持有虚引⽤,那么它就和没有任何引⽤⼀样,在任何时候都可能被垃圾回收器回收。
虚引⽤主要⽤来跟踪对象被垃圾回收器回收的活动。虚引⽤与软引⽤和弱引⽤的⼀个区别在于:虚引⽤必须和引⽤队列(ReferenceQueue)联合使⽤。当垃圾回收器准备回收⼀个对象时,如果发现它还有虚引⽤,就会在回收对象的内存之前,把这个虚引⽤加⼊到与之 关联的引⽤队列中。
1 为什么需要使⽤软引⽤
⾸先,我们看⼀个雇员信息查询系统的实例。我们将使⽤⼀个Java语⾔实现的雇员信息查询系统查询存储在磁盘⽂件或者数据库中的雇员⼈事档案信息。作为⼀个⽤户,我们完全有可能需要回头去查看⼏分钟甚⾄⼏秒钟前查看过的雇员档案信息(同样,我们在浏览WEB页⾯的时候也经常会使⽤“后退”按钮)。
这时我们通常会有两种程序实现⽅式:
⼀种是:玻利瓦尔
把过去查看过的雇员信息保存在内存中,每⼀个存储了雇员档案信息的Java对象的⽣命周期贯穿整个应⽤程序始终;
另⼀种是:
当⽤户开始查看其他雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引⽤,使得垃圾收集线程可以回收其所占⽤的内存空间,当⽤户再次需要浏览该雇员的档案信息的时候,重新构建该雇员的信息。
很显然,第⼀种实现⽅法将造成⼤量的内存浪费.
⽽第⼆种实现的缺陷在于即使垃圾收集线程还没有进⾏垃圾收集,包含雇员档案信息的对象仍然完好地保存在内存中,应⽤程序也要重新构建⼀个对象。
我们知道,访问磁盘⽂件、访问⽹络资源、查询数据库等操作都是影响应⽤程序执⾏性能的重要因素,如果能重新获取那些尚未被回收的Java对象的引⽤,必将减少不必要的访问,⼤⼤提⾼程序的运⾏速度。

本文发布于:2023-07-12 22:19:56,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1078987.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:对象   回收   垃圾   雇员   信息   内存   没有   档案
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图