JVM中对象判定生死依据

程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,这几个区域的内存分配和回收都具备确定性,当方法结束或者线程结束时,内存自然就跟随着回收了

Java堆和方法区这两个区域则有着很显著的不确定性:一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理

学习目的

  1. 当需要排查各种内存溢出、内存泄漏问题时
  2. 当垃圾收集成为系统达到更高并发量的瓶颈时

##

我们就必须对这些“自动化”的技术实施必要的监控和调节

解决问题

  1. 哪些内存需要回收?
  2. 什么时候回收?
  3. 如何回收?

对象已死?

垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(“死去”即不可能再被任何途径使用的对象)了。

引用计数算法 (已被淘汰)

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的

带来的弊端:引用计数算法无法解决互相嵌套引用对象

 // 运行的时候 加 vm参数  -XX:+PrintGCDetails
@Test
public void testGC() {
    ReferenceCountingGC objA = new ReferenceCountingGC();
    ReferenceCountingGC objB = new ReferenceCountingGC();
    objA.instance = objB;
    objB.instance = objA;
    objA = null;
    objB = null;
    // 假设在这行发生GC,objA和objB是否能被回收?
    System.gc();
}

虚拟机并没有因为这两个对象互相引用就放弃回收它们,这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象是否存活的。

可达性分析算法

通过”GCRoots”所引用的对象作为起始点,从这个节点向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GCRoots没有任何ReferenceChain相连时,则证明这个对象不可用。

如下图所示:object5、object6、object7虽然互相有关联,但是他们到GC Roots是不可达的,因此他们可能会被回收。

GC Roots.png

可作为GC Roots的对象

·在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

·在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。

·在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。

·在本地方法栈中JNI(即通常所说的Native方法)引用的对象。

·Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

·所有被同步锁(synchronized关键字)持有的对象。

·反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

再谈引用

在JDK 1.2版之前,Java里面的引用是很传统的定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。这种定义并没有什么不对,只是现在看来有些过于狭隘了,一个对象在这种定义下只有“被引用”或者“未被引用”两种状态,显然已经不能满足现代的软件编程应用场景,例如缓存

在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为4种,这4种引用强度依次逐渐减弱。

强引用(Strongly Re-ference)

只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象

软引用(Soft Reference)

是用来描述一些有用但并不是必需的对象,对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象

在Java中用java.lang.ref.SoftReference类来表示。

弱引用(Weak Reference)

当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。用java.lang.ref.WeakReference类来表示

虚引用(Phantom Reference)

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知

在java中用java.lang.ref.PhantomReference类表示

生存还是死亡?

即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程

  1. 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记
  2. 随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。;如果对象这时候还没有逃脱,那基本上它就真的要被回收了

最后的救赎

上面提到了判断死亡的依据,但被判断死亡后,还有生还的机会。
如何自我救赎:
1.对象重写了finalize()方法(这样在被判死后才会调用此方法,才有机会做最后的救赎);
2.在finalize()方法中重新引用到"GC Roots"链上(如把当前对象的引用this赋值给某对象的类变量/成员变量,重新建立可达的引用).

finalize()的调用具有不确定行,只保证方法会调用,但不保证方法里的任务会被执行完(比如一个对象手脚不够利索,磨磨叽叽,还在自救的过程中,被杀死回收了)。一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,因此第二次回导致自救行动失败了。

回收方法区

《Java虚拟机规范》规定虚拟机可以不实现垃圾收集,因为和堆的垃圾回收效率相比,方法区的回收效率实在太低,但是此部分内存区域也是可以被回收的。

方法区的垃圾回收主要有两种,分别是对废弃常量的回收和对无用类的回收

当一个常量对象不再任何地方被引用的时候,则被标记为废弃常量,这个常量可以被回收。

方法区中的类需要同时满足以下三个条件才能被标记为无用的类:

1.Java堆中不存在该类的任何实例对象;

2.加载该类的类加载器已经被回收;

3.该类对应的java.lang.Class对象不在任何地方被引用,且无法在任何地方通过反射访问该类的方法。

当满足上述三个条件的类才可以被回收,但是并不是一定会被回收,需要参数进行控制,例如HotSpot虚拟机提供了-Xnoclassgc参数进行控制是否回收。

 编程七大原则和23种设计模式汇总整理
JVM运行时数据分区介绍 
上一篇:编程七大原则和23种设计模式汇总整理
下一篇:JVM运行时数据分区介绍
评论

如果我的文章对你有帮助,或许可以打赏一下呀!

支付宝
微信
QQ