JVM系列--彻底搞清楚怎么确定一个对象是垃圾对象?

关注Java后端技术栈

回复“面试”获取最新资料

本文内容

JVM系列--彻底搞清楚怎么确定一个对象是垃圾对象?_第1张图片

如何确定一个对象是垃圾对象?

对于一部分人来说先搞清楚,对象与对象引用到底是什么区别?如果还不是很确定这两者的关系,请看:浅谈Java中的对象和对象引用

Java是面向对象编程的语言,都说在Java世界里万事万物皆对象。

类的声明周期和对象的声明周期。关于类的声明周期请看:JVM系列——java文件到JVM中的整个过程

对象的生命周期

下面大致说说对象的生命周期。

JVM系列--彻底搞清楚怎么确定一个对象是垃圾对象?_第2张图片

创建阶段

在创建阶段有几个步骤完成对象的创建:

  • 为对象分配存储空间

  • 开始构造对象

  • 从超类到子类对static成员进行初始化

  • 超类成员变量按顺序初始化,递归调用超类的构造方法

  • 子类成员变量按顺序初始化,子类构造方法调用

一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段

应用阶段

该对象应该至少被一个强引用持有着。

可以见阶段

当一个对象处于不可见阶段时,说明程序本身不再持有该对象的不论什么强引用,尽管该这些引用仍然是存在着的。简单说就是程序的运行已经超出了该对象的作用域了。

不可达阶段

对象处于不可达阶段是指该对象不再被不论什么强引用所持有。

与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的不论什么强引用,这样的情况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会导致对象的内存泄露情况,无法被回收。

收集阶段

当垃圾回收器发现该对象已经处于“不可达阶段”而且垃圾回收器已经对该对象的内存空间又一次分配做好准备时,则对象进入了“收集阶段”。假设该对象已经重写了finalize()方法,则会去运行该方法的终端操作。

这里要特别说明一下:不要重载finazlie()方法!finial方法来自Object中。参考:

原因有两点:

  • 会影响JVM的对象分配与回收速度

在分配该对象时,JVM需要在垃圾回收器上注冊该对象,以便在回收时可以运行该重载方法;在该方法的运行时需要消耗CPU时间且在运行完该方法后才会又一次运行回收操作,即至少需要垃圾回收器对该对象运行两次GC。

  • 可能造成该对象的再次“复活”

在finalize()方法中,假设有其他的强引用再次持有该对象,则会导致对象的状态由“收集阶段”又又一次变为“应用阶段”。这个已经破坏了Java对象的生命周期进程,且“复活”的对象不利用兴许的代码管理。

终结阶段

当对象运行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。

对象空间又一次分配阶段

垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间又一次分配阶段”。

Java对象的四大引用

先来了解对象的四大引用方式:

JVM系列--彻底搞清楚怎么确定一个对象是垃圾对象?_第3张图片

 JDK从1.2开始增加了多种引用方式:软引用、弱引用、虚引用 。

强引用

    /**
     * /强引用
     */
    private void strangeReference() {
        String str = new String("");
        System.out.println(str);
        UserVo [] userVo = new UserVo[1000000000];
        System.out.println(userVo.toString());
    }

方法中就算内存不够了而导致OOM,也不会进行回收这种引用。也是我们平时对象使用方式最多的方式。

弱引用

/**
 * 软引用
 */
private void softReference() {
    Object obj = new Object();
    SoftReference sf = new SoftReference(obj);
    System.out.println(sf.toString());
} 
   

如果一个对象具有软引用,内存足够GC就不会回收它,内存不足时就会回收这些对象的内存,故可以防止内存 泄露,增强代码的健壮性 。软引用用来实现内存敏感的高速缓存,比如说:网页缓存、图片缓存等。

软引用

    /**
     * 弱引用
     */
    private void weakReference() {
        String str = new String("abc");
        WeakReference abcWeakRef = new WeakReference(str);
    }

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

经典实用案列java.langThreadLocal

JVM系列--彻底搞清楚怎么确定一个对象是垃圾对象?_第4张图片

ThreadLocal相关请参考:
ThreadLocal造成内存溢出OOM面试再问ThreadLocal,别说你不会
再谈ThreadLocal
ThreadLocal 面试六连问,中高级必问

虚引用

    /**
     * 虚引用
     */
    private void phantomReference() {
        String str = new String("abc");
        ReferenceQueue rq = new ReferenceQueue();
        PhantomReference phantomRef = new PhantomReference(str, rq);
    }

如果一个对象仅持有虚引用,那么它就和没有引用一样,在任何时候都可以被垃圾回收期收回     作用:跟踪对象被垃圾回收器回收的活动。

如何确定一个对象是垃圾对象?

要想进行垃圾回收,得知道什么样的对象是垃圾。

引用计数法

对于某个对象而言,只要应用程序中持有该对象的引用,就说明对象不是垃圾,如果一个对象没有任何引用指向它,那么改对象就是垃圾对象。

在对象头处维护一个counter,每增加一次对该对象的引用计数器自加,如果对该对象的引用失联,则计数器自减。当counter为0时,表明该对象已经被废弃,不处于存活状态。这种方式一方面无法区分软、虛、弱、强引用类别。另一方面,会造成死锁,假设两个对象相互引用始终无法释放counter,永远不能GC

弊端:如果一个对象A持有对象B,而对象B也持有一个对象A,那发生了类似操作系统中死锁的循环持有,这种情况下A与B的counter恒大于1,会使得GC永远无法回收这两个对象 。

可达性分析

通过一系列为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的。如果对象在进行可行性分析后发现没有与GC Roots相连的引用链,也不会理解死亡。它会暂时被标记上并且进行一次筛选,筛选的条件是是否与必要执行finalize()方法。如果被判定有必要执行finaliza()方法,就会进入F-Queue队列中,并有一个虚拟机自动建立的、低优先级的线程去执行它。稍后GC将对F-Queue中的对象进行第二次小规模标记。如果这时还是没有新的关联出现,那基本上就真的被回收了。

JVM系列--彻底搞清楚怎么确定一个对象是垃圾对象?_第5张图片

那么那些点可以作为GC Roots呢?一般来说,如下情况的对象可以作为GC Roots

  1. 虚拟机栈(栈桢中的本地变量表)中的引用的对象

  2. 方法区中的类静态属性引用的对象

  3. 方法区中的常量引用的对象

  4. 本地方法栈中JNI(Native方法)的引用的对象

对于大多数人来说主要是理解上面这四点。要通过这四点去反推,去领悟为什么能把他们当做GC Roots。把四点理解清楚了,理解的这GC Roots就简单了。

往期精彩:

面试被问:缓存击穿有什么解决方案

面试被问:5 亿整数的大文件,排个序 ?

面试被问:高并发下的接口幂等性

面试官问:浏览器输入 URL 回车之后发生了什么?

面试题:数据量很大,分页查询很慢,有什么优化方案?

凭借这套面试题,工资狂涨60%!!!

在这金三银四的季节,栈长为大家准备了几份面试宝典:

  • 《java面试宝典5.0》

  • 《Java(BAT)面试必备》

  • 《350道Java面试题:整理自100+公司》

  • 《资深java面试宝典-视频版》

  • 大量电子书籍

获取方式:点“在看”,V信扫描上面二维码领取。

你可能感兴趣的:(JVM系列--彻底搞清楚怎么确定一个对象是垃圾对象?)