JVM-4-对象已死?

6 篇文章 0 订阅

读者朋友,晚上好,这里简单介绍下在JVM中是如何判断对象是否存活的。只有死去的对象,GC过程就会回收掉,所以GC的第一步是判断对象是否已死。

文章内容均来自:

《深入理解Java虚拟机 JVM高级特性与最佳实践》 周志明著 第三版
机械工业出版社

判断对象是否存活有2种方法:

一、判断对象存活的2种算法

方法原理优点缺点
引用计数法(Reference Counting)对象中添加一个引用计数器,有一个地方引用,就+1;引用失效就-1简单易懂相互循环引用,造成“假引用”
可达性分析法(Reachability Analysis )从GC Roots的跟对象作为起始节点集,从这些节点开始,根据引用关系向下搜索(图搜索),搜索走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots没有任何引用链,也就是对象不可达时,表明此对象是不可能再被使用的主流Java虚拟机都用的是这种

那么什么是: GC Roots呢?

栈: 在栈帧中引用的对象。方法栈帧中使用的参数、局部变量、临时变量
方法区:常量引用的对象
本地方法区域:Native方法引用的对象
虚拟机内部引用:基本包装类、异常对象(NPE 、OutofMemory)
所有被同步锁持有的对象
……

所以GC Roots不是一个点,而是一个集合,也就是GC Root Set .

看一下引用计数法的示例:

package com.cmh.concurrent.jvm;

/**
 * Author: 起舞的日子
 * Date:2021/5/24 1:49 下午
 * <p>
 * VM Options: -Xlog:gc*        旧版本:-XX:+PrintGCDetails
 */
public class ReferenceCountingGC {

    public Object instance = null;

    private static final int _1MB = 1024 * 1024;

    /**
     * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过
     */
    private byte[] bigSize = new byte[2 * _1MB];

    public static 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();
    }

    public static void main(String[] args) {
        testGC();
    }


}

输出的log如下:

[0.010s][info][gc] Using G1
[0.011s][info][gc,init] Version: 15.0.1+9-18 (release)
[0.011s][info][gc,init] CPUs: 8 total, 8 available
[0.011s][info][gc,init] Memory: 8192M
[0.011s][info][gc,init] Large Page Support: Disabled
[0.011s][info][gc,init] NUMA Support: Disabled
[0.011s][info][gc,init] Compressed Oops: Enabled (Zero based)
[0.011s][info][gc,init] Heap Region Size: 1M
[0.011s][info][gc,init] Heap Min Capacity: 8M
[0.011s][info][gc,init] Heap Initial Capacity: 128M
[0.011s][info][gc,init] Heap Max Capacity: 2G
[0.011s][info][gc,init] Pre-touch: Disabled
[0.011s][info][gc,init] Parallel Workers: 8
[0.011s][info][gc,init] Concurrent Workers: 2
[0.011s][info][gc,init] Concurrent Refinement Workers: 8
[0.011s][info][gc,init] Periodic GC: Disabled
[0.012s][info][gc,cds ] Mark closed archive regions in map: [0x00000007bff00000, 0x00000007bff78ff8]
[0.012s][info][gc,cds ] Mark open archive regions in map: [0x00000007bfe00000, 0x00000007bfe50ff8]
[0.012s][info][gc,metaspace] CDS archive(s) mapped at: [0x0000000800000000-0x0000000800b2b000-0x0000000800b2b000), size 11710464, SharedBaseAddress: 0x0000000800000000, ArchiveRelocationMode: 0.
[0.012s][info][gc,metaspace] Compressed class space mapped at: 0x0000000800b2c000-0x0000000840b2c000, size: 1073741824
[0.012s][info][gc,metaspace] Narrow klass base: 0x0000000800000000, Narrow klass shift: 3, Narrow klass range: 0x100000000
[0.115s][info][gc,task     ] GC(0) Using 3 workers of 8 for full compaction
[0.115s][info][gc,start    ] GC(0) Pause Full (System.gc())
[0.115s][info][gc,phases,start] GC(0) Phase 1: Mark live objects
[0.116s][info][gc,phases      ] GC(0) Phase 1: Mark live objects 0.767ms
[0.116s][info][gc,phases,start] GC(0) Phase 2: Prepare for compaction
[0.116s][info][gc,phases      ] GC(0) Phase 2: Prepare for compaction 0.267ms
[0.116s][info][gc,phases,start] GC(0) Phase 3: Adjust pointers
[0.117s][info][gc,phases      ] GC(0) Phase 3: Adjust pointers 0.324ms
[0.117s][info][gc,phases,start] GC(0) Phase 4: Compact heap
[0.117s][info][gc,phases      ] GC(0) Phase 4: Compact heap 0.468ms
[0.118s][info][gc,heap        ] GC(0) Eden regions: 5->0(5)
[0.118s][info][gc,heap        ] GC(0) Survivor regions: 0->0(0)
[0.118s][info][gc,heap        ] GC(0) Old regions: 0->4
[0.118s][info][gc,heap        ] GC(0) Archive regions: 2->2
[0.118s][info][gc,heap        ] GC(0) Humongous regions: 6->0
[0.118s][info][gc,metaspace   ] GC(0) Metaspace: 575K(4864K)->575K(4864K) NonClass: 525K(4352K)->525K(4352K) Class: 50K(512K)->50K(512K)
[0.118s][info][gc             ] GC(0) Pause Full (System.gc()) 11M->3M(20M) 2.553ms
[0.118s][info][gc,cpu         ] GC(0) User=0.01s Sys=0.00s Real=0.00s
[0.118s][info][gc,heap,exit   ] Heap
[0.118s][info][gc,heap,exit   ]  garbage-first heap   total 20480K, used 3072K [0x0000000780000000, 0x0000000800000000)
[0.118s][info][gc,heap,exit   ]   region size 1024K, 1 young (1024K), 0 survivors (0K)
[0.118s][info][gc,heap,exit   ]  Metaspace       used 578K, capacity 4499K, committed 4864K, reserved 1056768K
[0.118s][info][gc,heap,exit   ]   class space    used 50K, capacity 389K, committed 512K, reserved 1048576K

说实话,这里看不出来啥名堂,但是可以大体知道以下信息:

  • -Xlog的输出格式是 uptime 日志级别 标签
  • 垃圾收集器是:G1 Garbage First
  • 从gc start到gc finish要经历这几个过程: gc phases (标记对象间的引用)⇒ gc heap (分配对象) ⇒ gc metaspace ?

二、引用

可达性算法里面判断对象在 “引用图”中的可达性,是根据对象是否引用来判定的。对象并非简单的只有:“ 被引用 ” 或者 “未被引用”两种状态。

  • 强引用 Strongly Reference
  • 软引用 Soft Reference
  • 弱引用 Weak Reference
  • 虚引用 Phantom Reference

强度:
强引用 > 软引用 > 弱引用 > 虚引用

引用名称回收时机实现方式
强引用永不会被回收直接new的 Object obj = new Object()
软引用纳入回收范围,第二次回收SoftReference来实现软引用
弱引用必定回收WeakReference实现弱引用
虚引用不对其生存实现构成影响,一个对象即便有虚引用,那也跟没有引用一样;无法通过虚引用来取得一个对象实例PhantomReference来实现虚引用

这里的关键是,他们具体的应用场景是什么?留作思考题,后续补充。

三、对象逃脱死亡

看一下书中的例子:


package com.cmh.concurrent.jvm;

/**
 * Author: 起舞的日子
 * Date:2021/5/24 11:30 下午
 * <p>
 * 代码演示:
 * 1、对象可以在GC时自我拯救
 * 2、这种拯救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
 */
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes,i am still alive :)1");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws InterruptedException {
        SAVE_HOOK = new FinalizeEscapeGC();

        // 对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();

        // 因为finalize()方法优先级很低,暂停0.5s,等待它。
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }

        //下面这段代码和上面完全相同,但是这次却自救失败了
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }


    }

    /*
     * 第二次拯救失败是因为:
     * 任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临
     * 下一次回收,它的finalize()方法不会被再次执行。
     */
}


运行结果:

在这里插入图片描述

当然,finalize()方法从1.9开始标记为废弃了,所以并不推荐用这种方式来逃逸死亡。

再会。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值