JVM中Java对象的创建

这里不仅仅是说“对象的创建”,而是站在JVM的角度来看对象的创建过程、内存分配和访问定位,使用对象的创建是为了帮助大家索引进来

1. JVM中对象的创建过程

在语言层面上创建对象通常仅仅是一个new关键字,而在虚拟机中,对象(普通对象,不包括数组和Class对象)的创建时怎样的过程呢?

当JVM遇到一个new指令时,

  1. 首先,检查这个new指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化。如果没有,则会先进行类加载过程。

  2. 在类加载通过之后,JVM将为新生对象分配内存。对象所需内存大小在类加载完之后便可以完全确定,所以为对象分配空间就是将一块确定大小的内存从Java堆中划分出来。分配方式通常有两种:

    1. 指针碰撞(Bump the Pointer)
      Java堆中内存是规整的,被占用的内存集中在一边,并且用一个指针标识分界点。此时,分配内存就是将分界点指针向空闲方向移动此对象的大小的距离

    2. 空闲列表(Free List)
      Java堆中内存并不规整,此时JVM需要维护一个列表用于记录那些内存块可用,分配时从列表中找出合适的一块空间划分给对象

    所以,使用哪种分配方式由Java堆是否规整决定,而堆是否规整又由所采用的GC是否带有压缩整理功能决定。因此:

    1. 指针碰撞采用者是带compact过程的收集器:Serial、ParNew等
    2. 空闲列表采用者是Mark-Sweep算法收集器:CMS(Concurrent Mark Sweep)

    还有一个问题是:分配内存操作在并发情况下的线程安全问题,有两种解决方案:

    1. 对分配内存动作进行同步处理
      实际上JVM采用CAS配合上失败重试的方式保证更新操作的原子性
    2. 把内存分配按照线程划分在不同的空间之中进行
      即为每个线程在Java堆中预先分配一小块内存,称为:本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。这样,先在TLAB上分配,当TLAB用完时分配新的TLAB此时同步锁定。参数-XX:+/-UseTLAB
  3. JVM将对象分配的内存空间初始化为零值,如果使用TLAB,TLAB分配时进行这一部分。

  4. 设置对象头(Object Header),例如这个对象是哪个类的实例、如果找类元数据信息、对象哈希码、对象GC分代年龄等。

    以上(上面就是new指令的全部内容),在JVM的角度看一个新对象已经产生了,但是从Java程序视角来看,对象创建才刚刚开始,因为<init>方法还没有执行,所有字段还都为零。

  5. 所以,执行new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样,一个真正可用的对象才算完全产生。


2. 对象的内存布局

在HotSpot中,对象的内存布局可以分为3块区域:对象头(Object Header)、实例数据(Instance Data)以及对齐填充(Padding)。

  1. 对象头(Object Header),对象头包括两部分:

    1. 第一部分官方称为:Mark Word (对象的运行时数据)

      • 哈希码
      • GC分代年龄
      • 锁状态标志
      • 线程持有的锁
      • 偏向线程ID
      • 偏向时间戳
    2. 第二部分存储是类型指针

      对象指向它的类元信息的指针,通过这个指针来确定对象是哪个类的实例。
      这部分并不一定需要,参考3. 对象的访问定位

    3. 如果对象是Java数组,那么还得有第三部分用于记录数组长度

  2. 实例数据(Instance Data)
    程序中定义的各种类型的字段内容,包括父类中定义的。

  3. 对齐填充(Padding)
    并不一定存在,HotSpot要求对象起始地址必须是8字节的整数倍,也就是说对象大小必须是8字节整数倍,对象头部分是符合的,所以如果实例数据部分没有对齐,就需要这部分来填充对齐。


3. 对象的访问定位

以上已经创建了对象,那么如果定位对象呢?

Java程序需要通过栈上的reference数据来操作堆上的具体对象,而JVM规范只规定了reference是一个指向对象的引用,没有规定reference如果定位和访问堆中对象的具体位置,所以对象的访问定位取决于具体的JVM实现,有两种主流方式:句柄和直接指针

  1. 句柄
    使用句柄方式的话,reference存储的是对象的句柄地址。此时会将JavaHeap划分为两部分:句柄池和实例池,而一个句柄包含两个指针:实例指针、类型指针

  2. 直接指针
    reference存储的直接是对象地址,但对象实例数据中自身就必须包含类型指针这部分数据了

可以想到,句柄的优势是灵活性好,reference中是不变的句柄地址,而对象位置改变只需要改变句柄池中实例指针地址就可以;而直接指针的好处是速度更快,节省了一次实例指针定位开销

HotSpot而言它采用了直接指针,但这并不是说句柄不行,主要看具体实现而已。


参考文献:

[ 1 ] 周志明.深入理解Java虚拟机[M].第2版.北京:机械工业出版社,2015.8.
[ 2 ] Tim Lindholm,Frank Yellin,Gilad Bracha,Alex Buckley.The Java® Virtual Machine Specification . Java SE 8 Edition . 英文版[EB/OL].2015-02-13.
[ 3 ] James Gosling,Bill Joy,Guy Steele,Gilad Bracha,Alex Buckley.The Java® Language Specification . Java SE 8 Edition . 英文版[EB/OL].2015-02-13.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值