JVM中的对象及引用(一)

对象的内存布局

在这里插入图片描述
在虚拟机(HotSpot)中,对象存储布局分为三个区域

对象头

对象头用于存储自身的运行时数据,如哈希码、GC分代年龄等,另一部分是类型指针,也就是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。如果说对象是一个数组,那么对象头里面还储存数据长度的数据。

实例数据

实例数据部分存储着对象程序代码中定义的各种类型字段内容,包括从父类继承的和子类总定义的。这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源代码中定义顺序的影响。虚拟机默认将相同长度的字段分配到一起,且父类定义的变量会出现在子类之前。通过配置虚拟机参数也可以使子类较窄的变量插到父类变量空隙中。

对齐填充

这个不是必然存在的,也没有什么含义,就是起到站位符的作用。因为对象头的大小规定是8个字节的整数倍,当对象头和实例数据没有对齐时,就需要通过对齐填充来补全。

对象的访问定位

当对象创建好后,我们需要通过栈上的reference数据操作堆上的具体对象。目前主流的访问方式有两种句柄和直接指针。

句柄

在这里插入图片描述
如果使用句柄的话,那么java堆中会划分出一块内存作为句柄池,reference中储存的对象就是句柄池的地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。使用句柄来访问最大的好处就是稳定,在对象被移动(垃圾回收时移动对象是普遍行为)时会改变句柄中的实例数据指针,而 reference 本身不需要修改。

直接指针

在这里插入图片描述
如果使用直接指针访问,reference 中存储的直接就是对象地址。
使用直接指针的最大好处就是速度快,它节省了一次指针定位的开销。

判断对象的存活

问:在堆中存放着几乎所有的对象实例,当垃圾回收器进行回收时,首先要做的事情就是判断那些对象还活着,那些已经死去

答:没有任何引用指向的一个或者多个对象。

引用计数法

在对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1,当引用失效时,计数器减 1。

在这里插入图片描述
但是引用计数法会存在上图问题,但两个对象互相引用的时候,是没有办法来处理,这个时候需要引入额外的机制来处理,从而比较影响
效率,因此主流虚拟机没有使用。

可达性分析

在这里插入图片描述
来判断对象是否存活,可达性分析算法思路就是通过一系列的称为 GC Roots的对象作为起点,往下节点搜索,搜索走过的路程称为引用链,当一个对象到 GC Roots没有任何引用链,则证明此对象不可用。
作为GC Roots 的对象包括下面几种(主要是前四种)

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;各个线程调用方法堆栈中使用到的参数、局部变量、临时变量等。
  2. 方法区中类静态属性引用的对象;java 类的引用类型静态变量。
  3. 方法区中常量引用的对象;比如:字符串常量池里的引用。
  4. 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。
  5. JVM 的内部引用(class 对象、异常对象 NullPointException、OutofMemoryError,系统类加载器)。
  6. 所有被同步锁(synchronized 关键)持有的对象。
  7. JVM 内部的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。
  8. JVM 实现中的“临时性”对象,跨代引用的对象。

Class回收条件

以上的回收都是对象,那么class要被回收,条件比较苛刻(有参数可以进行控制),必须满足一下条件:
1、该类所有实例都被回收,也就是堆中没有该类的任何实例。
2、加载该类的ClassLoader 已经被回收。
3、该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
4、参数控制在这里插入图片描述
废弃的常量和静态变量的回收其实就和 Class 回收的条件差不多。

Finalize 方法

即使通过可达性分析判断不可达的对象,也不是“非死不可”,它还会处于“缓刑”阶段,真正要宣告一个对象死亡,需要经过两次标记过程,一次是 没有找到与 GCRoots 的引用链,它将被第一次标记。随后进行一次筛选(如果对象覆盖了 finalize),我们可以在 finalize 中去拯救。

上代码:

public class TestGC {

    public static TestGC instance = null;

    public void isAlive() {
        System.out.println("我还活着");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize 拯救");
        TestGC.instance = this;
    }

    public static void main(String[] args) throws Throwable {
        instance = new TestGC();
        //进行第一次GC
        instance = null;
        System.gc();
        Thread.sleep(1000);//finalize方法优先级很低,需要等待
        if (instance != null) {
            instance.isAlive();
        } else {
            System.out.println("我死了");
        }
        //进行第二次GC
        instance = null;
        System.gc();
        Thread.sleep(1000);
        if (instance != null) {
            instance.isAlive();
        } else {
            System.out.println("我死了");
        }
    }


运行结果:
在这里插入图片描述
可以看到,对象可以被拯救一次(finalize 执行第一次,但是不会执行第二次) 。

代码改一下,再来一次:

public class TestGC {

    public static TestGC instance = null;

    public void isAlive() {
        System.out.println("我还活着");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize 拯救");
        TestGC.instance = this;
    }

    public static void main(String[] args) {
        instance = new TestGC();
        //进行第一次GC
        instance = null;
        System.gc();
        if (instance != null) {
            instance.isAlive();
        } else {
            System.out.println("我死了");
        }
        //进行第二次GC
        instance = null;
        System.gc();
        if (instance != null) {
            instance.isAlive();
        } else {
            System.out.println("我死了");
        }
    }


}

运行结果:
在这里插入图片描述
对象没有被拯救,这个就是 finalize 方法执行缓慢,还没有完成拯救,垃圾回收器就已经回收掉了。 所以建议尽量不要使用 finalize,因为这个方法太不可靠。在生产中你很难控制方法的执行或者对象的调用顺序,建议忘了 finalize 方法!因为在 finalize 方法能做的工作,java 中有更好的,比如 try-finally 或者其他方式可以做得更好。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值