Java高级——对象

对象创建

JVM运行new指令时,先在常量池中查找该类的符号引用,并检查该类是否已加载、解析和初始化过,如果没有则需先执行类加载过程

随后在Java堆中分配内存,对象所需内存在类加载后便可完全确定,分配方式有两种:

  • 指针碰撞:内存是连续的,已使用内存和未使用内存以指针为界,指针向未使用内存移动一段与对象大小相等的距离
  • 空闲列表:内存不连续,未使用内存记录在列表,从列表中划分内存

在分配内存中需要保证线程安全,采用2种方式:

  • 分配时进行同步
  • 在堆中为每个线程预设本地线程分配缓冲区(TLAB),TLAB用完后分配新缓冲区时才需要同步,可通过-XX:+/-UseTLAB选择是否开启此功能

内存(TLAB)分配完成后都会被初始化为零值,只有在Class文件的<init>()方法(即java的构造函数)执行后才会初始化为正确的值

对象的存储结构

对象在堆中的存储结构分为3部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)

对象头

对象头包括如下信息:

  • 对象自身运行时数据,如HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,称为Mark Word

  • 类型指针,用于确实对象是哪个类的实例,并非所有虚拟机都有该实现

  • 若对象是数组还需要记录数组长度,普通对象则可根据元数据确认对象大小

实例数据

实例数据是代码中所定义的各种类型的字段内容,包括父类及自身的字段

存储顺序与JVM分配策略参数(–XX:FieldsAllocationStyle)及字段在Java中定义的顺序有关

默认分配顺序为longs/doubles、ints、shorts/chars、byte/boolean、Ordinary Object Pointers

默认父类变量出现在子类前,当+XX:CompactFields设为true,则允许子类中较窄的变量插入父类变量的空隙中

字节对齐

字节对齐起占位符作用,用于保证对象大小为8kb的整数倍,并不一定存在

对象的使用

对象的访问方式

通过栈上的reference类型数据指向对象的引用,有句柄和直接指针两种方式

在这里插入图片描述
上图为句柄,好处是reference存储的地址不会改变,对象被移动时只会改变句柄内部的指针

在这里插入图片描述
上图为直接指针,只需要一次定位,速度更快,Hotspot采用的就是这种方式

直接指针用于准确式内存管理的JVM中,虚拟机可以知道内存中某个位置的数据具体是什么类型,如内存中有一个整数123456,能分辨出它是一个指向了123456的内存地址的引用类型还是一个数值为123456的整数

而非准确式内存管理的JVM只能通过句柄保持引用的稳定(因为垃圾收集后对象会移动位置,无法确定修改的是地址还是数据)

对象的引用类型

强引用(Strongly Reference):类似Object obj = new Object()的引用关系,只有强引用还在,就不会回收被引用的对象

软引用(Soft Reference):一些还有用,但非必要的对象,其关联的对象,在系统OutOfMemoryError前进行回收

弱引用(Weak Reference):强度更弱,其关联的对象在下一次gc时被回收(无论当前内存是否充足都会回收)

虚引用(Phantom Reference):强度最弱,不会对生存时间构成影响,也无法通过虚引用获取实例,其存在目的是为了在对象被gc时收到一个系统通知

对象存活与否

引用计数算法(Reference Counting)

引用计数算法通过在对象中添加引用计数器

  • 每当有一个地方引用它时,计数器值就加一
  • 当引用失效时,计数器值就减一
  • 计数器为零的对象就是不可用的

引用计数算法原理简单、效率高,但有很多额外情况需要考虑,如循环引用

可达性分析算法(Reachability Analysis)

JVM采用的就是可达性分析算法,以GC Roots为根结点,根据引用关系向下搜索(搜索路径称为引用链),若某个对象到GC Roots间没有任何引用链相连,即不可达,则对象不可用

可作为GC Roots的对象有:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 在方法区中类静态属性引用的对象
  • 在方法区中常量引用的对象
  • 在本地方法栈中JNI引用的对象
  • Java虚拟机内部的引用
  • 所有被synchronized的对象
  • 反映JVM内部情况的JMXBean、本地代码缓存等

除此之外,根据垃圾收集器和回收的内存区域不同,还可以有其他对象临时成为GC Roots,如针对某一区域进行gc时,其关联区域的对象会成为GC Roots

对象的回收

若对象经过可达性分析后发现没有与GC Roots相连接的引用链,其会被第一次标记,随后进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法

下面两种情况无需执行finalize()方法

  • 对象没覆盖finalize()方法
  • finalize()方法已被JVM调用

若需要执行finalize()方法

  • 对象进入F-Queue队列
  • JVM创建的Finalizer线程去执行队列中对象的finalize()方法,对象可在finalize()中复活跳脱标记
  • 收集器将对队列中的对象进行第二次标记
  • 对标记对象进行回收

如下代码利用finalize()中复活对象

public class Test {

	public static Test SAVE_HOOK = null;
    
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize()");
        SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable{
        SAVE_HOOK = new Test();
        selfSave();
        selfSave();
    }

    private static void selfSave() throws InterruptedException {
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            System.out.println("alive");
        } else {
            System.out.println("dead");
        }
    }
	
}

如上调用两次selfSave()复活,一次成功一次失败,因为finalize()只会被系统自动调用一次

finalize()
alive
dead
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值