【Java】深入理解JVM学习笔记(二) —— 对象

    xiaoxiao2021-04-12  38

    【问题一】程序通过new一个普通对象的时候,虚拟机进行了哪些操作? 解答: ①首先检查这个指令的参数是否能在常量池中定位到这个类引用 ②并且检查这个符号引用代表的类是否被加载、解析、初始化过③若没有加载,必须执行相应类的类加载过程④虚拟机为新生对象分配内存(对象所需内存大小,在类加载时间可以完全确定)

    ④ 分配内存

    为对象分配大小相当于把一块确定大小的内存从Java堆中划分出来。假设Java内存是规整的,所有用过的内存放在一边,所有空闲的内存放在一边,中间放着一个指针作为分界点的指示器。对象大小就是指针向空闲内存移动的距离,这种分配方式叫做指针碰撞。假设内存是不规整的,已使用的空间和空闲空间相互交错,虚拟机须维护一个列表,存放可用内存空间,在分配的时候根据列表找到一块足够大的空间给对象,并更新列表上的记录,这种分配被称为“空闲列表” 总结:选择哪种分配方式是通过Java内存是否规整决定,而Java是否规整又由采集的垃圾收集器(GC)是否带有压缩整理功能决定

    Compact收集器采用指针碰撞方式,而Mark-Slweep采用空闲列表


    分配对象可能是线程不安全的,虚拟机采用2种方式保证更新操作的原子性

    CAS+失败重试方式每个线程预分配一小块内存(本地线程分配缓冲区TLAB)。哪个线程需要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB,才同步锁定

    使用TLAB

    -XX:+/-UseTLAB

    ⑤ 初始化

    虚拟机将分配到的内存空间初始化为0.如果使用TLAB,可将这一操作提前到分配TLAB时进行。 保证Java实例对象字段,不需要初始化,就可以直接使用。

    ⑥ 必要设置

    虚拟机对对象进行必要的设置。例如: 对象属于哪个类实例、如何找到类的元数据信息、对象哈希码、对象GC分代年龄等设置

    以上完成后。对于虚拟机来说,一个对象已经产生了,但是从Java的角度看,一个对象的创建刚刚开始——init()还未执行,所有字段还是0.

    注意:当一个对象初始化之后,一个真正可用的对象才算生成。


    对象的内存布局

    对象在内存中的布局包含:对象头、实例数据、对齐填充 HotSpot虚拟机对象头包括:存储自身对象运行数据和类型指针

    存储自身对象 哈希码、GC分代年龄、锁标志、线程持有锁、偏向线程ID、偏向时间戳等类型指针 通过这个指针确定这个对象属于哪个类实例。但是不是所有虚拟机都必须在对象数据类型保留类型指针,所以查找对象的元数据信息不一定要经过类本身。如果是一个数组,还需要记录长度数据。

    实例数据字段分配策略顺序:longs/doubles -> int -> short/chars -> byte/boolean -> oops

    相同宽度的字段总是分配在一起父类定义的变量总是在子类之前对其填充仅仅起占位符的作用

    对象访问定位

    通过栈上的reference数据操作堆上数据 reference只提供了一个指向对象的引用,访问定位主流方式有使用句柄和直接指针两种方式实现

    访问方式

    使用句柄

    Java堆划分一块内存作为句柄池,reference存储对象句柄地址。而句柄中包含对象实例数据和类型数据各自的地址信息

    使用直接指针

    reference存储的是对象地址

    句柄访问方式

    直接指针访问方式

    句柄访问优点:在对象移动时,改变的只是句柄中的实例数据本身,而reference本身并不改变

    直接指针的优点:访问速度快,节省了指针定位的开销

    -
    转载请注明原文地址: https://ju.6miu.com/read-667830.html

    最新回复(0)