深入理解Java虚拟机(堆对象)

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

文章收录在网站:http://hardyfish.top/

在这里插入图片描述

堆对象

对象的创建

遇到一条New指令,虚拟机的步骤:

  • 检查这个指令的参数能否在常量池中定位到一个类的符号引用

    • 并检查这个符号引用代表的类是否已被加载、解析和初始化过
  • 如果没有,必须先把这个类加载进内存

类加载检查通过后,虚拟机将为新对象分配内存,类加载完就可以确定存储这个对象所需的内存大小

将分配到的内存空间初始化为零值

设置对象头(Object Header)中的数据:

  • 包括这个对象是哪个类的实例、如何才能找到类的元数据、对象的哈希码(实际在调用Object::hashCode()方法才计算)、对象的GC分代年龄等

此时从虚拟机的角度看,对象已经产生,但从 Java 程序的角度看,构造函数还没有执行

执行完初始化函数,一个真正的对象才算完全构造出来

在第二步中,为对象分配内存,就是在内存划分一块确定大小的空闲内存,但存在两个问题:

如何划分空闲内存和已被使用的内存?

  • 假设Java堆中内存是绝对规整的,空闲内存和被使用内存被分到两边,中间放置指针作为分界点的指示器

    • 那分配内存就是把指针向空闲内存一定一段,这种方式成为 指针碰撞(Bump The Pointer
  • 但如果Java堆内存不是规整的,那就没有办法简单地进行指针碰撞了,虚拟机需要维护一个列表

    • 记录哪些内存块可以使用,在分配内存的时候,找到一块足够打的内存划分给对象实例
      • 并更新列表上的记录,这种方式被称为 空闲列表(Free List)
  • 事实上,这由所采用的垃圾收集器是否带有空间压缩整理(Compact)的能力决定

  • 因此,当使用Seria、parNew等带压缩整理过程的收集器时,系统采用的分配算法是指针碰撞,既简单又高效

  • 而当使用CMS这种基于清除(Sweep)算法的收集器时,理论上只能采用较为复杂的空闲列表来分配内存

如何处理多线程下,内存分配问题?

  • 对分配内存空间的动作进行同步处理,实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性
  • 把内存分配的动作按照线程划分在不同的空间之中进行
    • 即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Theard Local Allocation Buffer, TLAB
    • 哪个线程要分配内存就先在本地线程分配缓冲中分配,只有缓冲使用完了,分配新的缓存区时需要同步锁定
  • 通过-XX:+/-UseTLAB 参数设置是否使用TLAB

对象的内存布局

对象头(Header):

  • 第一部分:存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等

    • (官方称之为 Mark Word
  • 第二部分:类型指针,即对象指向它的类型元数据的指针,虚拟机通过这个指针来判断这个对象是哪个类的实例

    • 如果是数组对象,将会在对象头存储数组长度,以确定对象大小

实例数据(Instance Data):

  • 在程序代码里面所有定义的各种类型的字段内容都必须记录。

  • 这部分的存储顺序受到虚拟机分配策略参数(-XX:FieldsAllocationStyle 参数)和Java源码中定义顺序的影响

  • HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops (Ordinary Object Pointers, OOPs)

    • 相同宽度的字段总是被分配到一起存放,满足这个条件的前提下,父类定义的变量会出现在子类之前
  • 对齐填充(Padding):

    • HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍
    • 因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全

对象的访问定位

Java程序通过栈上的reference数据来操作堆上具体对象,主流的访问方式主要有以下两种:

  • 使用句柄:Java堆可能会划分一块内存作为句柄池,reference中存储的就是对象的句柄地址
    • 而句柄包含了对象实例数据与类型数据各自Juin的地址信息
  • 优势:在对象被移动(垃圾收集时移动对象是非常普遍的行为)时
    • 只会改变句柄中的实例数据指针,而不需修改reference

在这里插入图片描述

使用直接指针:Java堆中对象的内存布局必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址

  • 如果只是访问对象本身,就不需要多一次间接访问的开销

优势:速度快,节省一次指针定位的时间开销

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值