Java对象的创建

在java中可以通过:

  • new关键字
  • 反序列化。ObjectInputStream.readObject()
  • 反射。Class.newInstance()
  • 对象克隆。Object.clone()

来创建一个对象

分配内存

当创建一个对象时,需要在堆中分配一块内存(当开启逃逸分析时,可能基于栈上分配标量替换等原因,直接在栈上分配),用于容纳此对象。从宏观上看,可以分为两部分:快速分配和慢分配。具体流程如下:

  • 如果当前类还未加载,则会直接进入慢分配流程,先加载类,然后再执行对象的创建

  • 如果当前方法已经被JIT编译为机器代码,切逃逸分析表明对象并未逃逸,且对象并不是太大,没有太多的实例字段,则会优先使用栈上分配,直接在当前方法栈上分配对象

  • 如果未达到栈上分配的条件。则会优先使用TLAB。如果开启了TLAB(-XX:+UseTLAB,默认开启),即每个线程在Java堆的eden区中会预先分配一小块内存(默认是eden的1%),那么jvm会优先使用这部分内存,因为是线程私有,不需要cas或者锁操作,效率更高

  • 如果TLAB条件也不满足,则会优先在eden区分配对象。此时,根据jvm所使用的gc有无Compact过程,分配内存的方式有两种:

    • 如果gc后,会Compact,即将使用的内存放到一边,未使用的放到另一边。这种情况下,已使用和未使用的内存中会存在一个指针,分配指定大小内存仅仅就是将这个指针移动与对象大小相等的距离。这种方式称为指针碰撞
    • 如果gc后不会Compact,此时jvm需要维护一个空闲列表,记录哪些内存是可用的,从可用的内存中找到一块足以容纳对象大小的内存,分配使用

    由于内存分配是十分频繁的行为,所以jvm采用首先CAS机制保证分配的线程安全,如果失败则会采用互斥锁的方式。

  • 如果eden分配失败(无空间)或者对象满足了直接进入老年代的条件,那就直接分配在老年代。

当内存分配完毕时,jvm会进行内存的清零操作,即将所有内存填充二进制的0.(如果是从TLAB分配的,则没有这一步,因为TLAB的内存已经清零了)

再之后,会设置对象的对象头,对象头中有一部分用于偏向锁的设置,如果开启了偏向锁,则会将分配内存的这个线程设置到对象头的偏向锁部分。

对象分配的完成流程如下:

在这里插入图片描述

对象的内存表示

在hotspot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(object header)、实例数据、对齐填充(padding)。

对象头包括两部分信息,第一部分用那个与存储对象自身的运行时数据,如锁信息、gc年龄等。这部分大小在32位和64位虚拟机中分别为32bit和64bit。官方称为 Mark Word;另一部分为类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据部分是对象真正存储的有效信息,也就是代码中一个类的实例字段,包括自己的和从父类继承的字段

padding并不是必须的,也无特殊含义,只是HotSpot要求对象的起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍。对象头部分的大小正好是8字节的倍数,因此当实例数据部分没有对齐时,就需要通过padding来对齐

参考

以上内容参考:

周志明:《深入理解Java虚拟机》

封亚飞:《揭秘Java虚拟机》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值