JVM内存管理之对象管理

对象的创建

JVM的PC寄存器指向一条new指令,先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载,解析和初始化。若未,则必须执行相应类的加载过程。
在类加载检查通过后,接下来JVM为新建对象分配内存。对象所需内存在类加载过程完成后便已确定,为对象分配内存就等同于在Java Heap中分配一块确定大小的内存块。
Java Heap规整式:
所有使用的内存放在一边,未使用的放在另一边,中间放有指针作为分界点指示器。分配内存仅仅就是将指针指示器向空闲内存区移动新建对象大小的内存的距离,该分配方式称为指针碰撞(Bump The Pointer)
Java Heap不规整式:
JVM将维护一个空闲内存块表,分配内存时查询空闲内存表进行分配,并更新空闲内存表。
Java Heap是否规整有所采用的垃圾收集器是否带有空间压缩整理(Compact)的能力决定。
当使用Serial,ParNew等带空间压缩整理过程的收集器,系统采用分配算法是指针碰撞,即简单又高效。
当使用CMS这种基于清除(Sweep)算法的收集器时,理论上就只能采用较为复杂的空闲列表来分配内存。

划分空间之外,还有另一个问题:对象创建是非常频繁的行为,即使仅仅修改一个指针所指向的位置,在并发情况下并不是线程安全的,可能出现正在给A分配内存,指针还未修改,对象B又同时使用了原来的指针来分配内存的情况。为解决以上问题,有两种可选方案:

  1. 对分配内存空间进行同步处理。
    实际上虚拟机是采用CAS(Compare And Swap)算法失败重试的方式保证更新操作的原子性
  2. 把内存分配动作按照线程划分在不同空间之中进行。
    每个线程在Java Heap预先分配内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。只有线程本地缓冲区用完时,分配新的缓冲区是才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:-UserTLAB参数设定。

内存分配后,JVM将分配到的内存空间(不包括对象头)都初始化为零值。如果使用了TLAB,该操作将提前至分配TLAB后进行,该操作保证了对象的实例字段在Java代码中可以不赋初始化值即可使用,使程序访问到这些字段的数据类型对应的零值。

JVM还要对对象进行必要的设置,例如该对象对应的类,如何找到类的元数据信息,对象的哈希吗(实际上对象的哈希吗会延后到真正调用Object::hashCode()方法调用时才计算),对象的GC分代年龄等信息。这些信息存放在对象的对象头之中。

之后,JVM将执行构造函数,即Class文件中的()方法,按照构造方法中的程序对对象进行赋值。

对象的内存布局

概图

在这里插入图片描述

在Hotspot虚拟机中,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header),实例数据(Instance Data)和对齐填充(Padding)。

对象头(Header):

对象头的两类信息:

  1. 用于存储对象自身的运行时数据。
    如哈希码,GC分代年龄,锁状态标志,线程持有锁,偏向线程ID,偏向时间戳等,这部分数据长度在32位和64位虚拟机中分别位32和64比特。官方称其为Mark Word。对象需要的运行时数据很多。已超过32,64位bitmap结构所能记录的最大限度。但对象头定义的数据是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个有着动态定义的数据结构,以便在极小的空间内存内存储更多的数据,根据对象状态复用自己的存储空间。
    以下为源码(markOpp.cpp)中的对32位虚拟机和64位虚拟机Mark Word的注释
//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//             size:32 ------------------------------------------>| (CMS free block)
//             PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
  1. 类型指针,即对象指向它的类型元数据的指针。
    JVM通过该指针来确定该对象是哪个类的实例。并不所有的虚拟机实现都必须在对象数据上保留类型指针,即查找对象的元数据信息并不是一定要经过对象本身。如果对象是一个Java数组,对象头必须有一个用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是如果数组长度是不确定的,将无法通过元数据中的信息推断出数组的大小。

实例数据(Instance Data):

对象所存储的有效信息,即在程序代码中所定义的各种类型的字段内容。这部分存储顺序会受到虚拟机分配策略(-XX:FieldsAllocationStyle参数)和字段在Java源码定义顺序的影响。Hotspot虚拟机默认顺序为longs/doubles,ints,shorts/chars,bytes/booleans,oops(Ordinary Object Points,OOPs)。从以上默认顺序可以看出,相同长度的字段会被一起存放在满足该条件下,父类变量放在子类变量之前。如果Hotspot的-XX:FieldsAllocationStyle参数为true(默认为true),则子类之中较小的变量则可以插入父类变量之中,以节省出一点点空间。

对齐填充(Padding):

类似占位符的作用,由于Hotspot虚拟机的自动内存管理要求对象起始地址必须是8字节的整数倍。对象头已被设计成8字节倍数,若实例数据部分不是8字节倍数,需要对齐填充成8字节倍数

对象的访问定位

Java线程通过栈上的reference类型数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中只规定了它是一个指向对象的引用,并没有定义这个引用应该通过什么方式定位,访问堆中对象的具体位置,所以对象访问方式也是由虚拟机实现而定,主流访问方式主要有使用句柄和直接指针两种:

  1. 句柄访问:
    Java堆中可能会划分一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据类型数据各自具体的地址信息
    在这里插入图片描述
  2. 直接指针访问:
    Java堆中对象的内存布局必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只考虑访问对象本身,比起句柄可以省去一次间接访问的开销。
    在这里插入图片描述

句柄优点:
reference中存储稳定句柄地址,对象被移动时只需改变句柄中的实例数据指针。

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

Hotspot使用的是直接指针访问,但从整个软件开发范围来看,在各种语言框架中使用句柄来访问更为流行。

总结

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值