JVM虚拟机如何判断对象死亡?(三)

JVM中的程序计数器,本地方法栈,虚拟机栈都为线程私有,随线程的产生而产生,消亡而消亡,这几个区域的内存分配和回收有确定性,而在Java堆和方法区中内存的回收具有不确定性,只有在运行期间才知道要去创建多少个对象,内存的分配和回收是动态的。

堆中存放着Java几乎所有的对象,垃圾收集器在对堆进行回收时,首先要确定堆中哪些对象还存活,那些对象已死去(没有任何用途的对象)。

回收Java堆区

判断对象是存活

引用计数法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加一;当引用失效时,计数器的值就减一,任何时刻计数器为零的对象是不可能在被使用的。
**优点:**原理简单,判定效率高。
**缺点:**占用了额外的内存。
在Java领域中,主流的Java虚拟机里面都没有选用引用计数器算法来管理内存
原因:必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数很难解决对象之间循环引用的问题。
例如:对象objA和objB都有字段instance,赋值令objA.instance = objB 及 objB.instance = objA。两个对象不能再被访问,因为他们计数器不为零,引用计数法无法回收他们。

可达性分析算法

目前Java通过可达性分析算法判断对象是否存活。算法基本思路通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的链称为“引用链”,如果对象到GC Roots间没有任何引用链,或者从GC Roots到这个对象不可达,则证明此对象是不可能再被使用的。
在这里插入图片描述
O1,O2,O3 不可回收,O4,O5,O6可回收。

可作为GC Roots的对象:

  1. 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如每个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量等。
  2. 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  3. 在方法区中常量引用的对象,譬如字符串常量池里的引用。
  4. 在本地方法栈中JNI(Native方法)引用的对象。
  5. Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻对象(NullPointException,OutOfMemoryError)等,还有系统类加载器。
  6. 所有被同步锁(synchronized关键字)持有的对象。
  7. 反应Java虚拟机内部情况的JMXBean,JVMTI中注册的回调。本地代码缓存等。
    除了这些固定的GC Roots集合外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有对象临时性的加入。

JDK1.2后出现的四种引用:强引用,软引用,弱引用,虚引用

可达性算法判断过程

当判定某一对象为不可达时,暂时不会马上回收,真正宣布一个对象死亡,至少要经历两次标记过程,如果进行可达性分析后没有发现与GC Roots的引用链,则会进行第一次标记,随后进行一次筛选,筛选的条件此对象是否有必要执行finalize()方法,如果对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将对这两种情况视为没有必要执行。
如果这个对象被判定为有必要执行finalize()方法,此对象会加入F-Queue的对列中,在稍后虚拟机自动建立的低调低优先级的Finalizer线程去执行他们的finalize()方法,但不一定会等待其运行结束(可能出现死循环)。
如果对象在finalize()方法中将自己与引用链中的某一对象建立关联,则可以不被回收。譬如将自己(this)赋值给某个类的变量或者对象的成员变量。不建议使用此方法

回收方法区

主要回收两部分内容:废弃的常量,不再使用的类型。

回收常量

没有对象引用此常量,并且虚拟机中也没有其他地方引用这个常量,如果这是发生内存回收,而且垃圾收集器判断确有必要的话,这个常量就将会被系统清理出常量池(如字符串常量)。常量池中其他类(接口),方法,字段的符号引用于此类似。

回收类型

  1. 该类型的实例都已经被回收,也就是Java堆中不存在该类,和该类的任何派生子类实例。
  2. 加载该类的类加载器已经被回收,很难达成,除非精心设计。
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    Java虚拟机被允许对同时满足上述三个条件的无用类进行回收。

猜你喜欢

转载自blog.csdn.net/weixin_43663421/article/details/109236203
今日推荐