*将以前写的一篇博客重新整理
1 对象的创建
对象的创建(仅限普通对象,不包括数组和Class对象)分为五个步骤:
第一步:类加载检查
虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就必须进行相应的类加载过程。
第二步:分配内存
类加载检查通过后,要进行分配内存。对象所需的内存大小在类加载完成后便可完全确定,为对象分配内存的任务便转化成把一块大小确定的内存从Java堆中划分出来。
有两种方式:“指针碰撞”和“空闲列表”:
- 指针碰撞:假设Java中内存是完整的,所有用过的内存放一边,没用的内存放另一边,中间放置一个指针作为分界点指示器。当需要分配内存时只需要把指针向空闲内存方向移动相应的大小即可。
- 空闲列表:假设Java堆的内存空间不规整,已使用的内存和空闲内存交错。虚拟机维护一张表记录那些内存块是可用的。在分配的时候从表中选出一个大小合适和内存块划分给对象实例。
这两种分配方式并没有绝对的好坏之分,有的虚拟机使用指针碰撞,有的虚拟机使用空闲列表。
除如何划分空间外,另一个问题是线程同步问题。因为即使移动一个指针,在并发情况下也可能是不安全的。解决这个问题同样有两种方案:
- 对分配空间的动作做同步处理
- 把内存分配操作按照线程划分在不同的空间中进行--每个线程在Java堆中预先划分出一小块内存,称为本地线程分配缓冲(TLAB)。哪个线程需要分配内存,就在哪个线程的TLAB上分配。只有TLAB用完并分配新的TLAB时,才需要同步锁定
第三步:初始化内存空间
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。如果使用TLAB,该工作可以提前至TLAB分配时进行。
第四步:对对象进行必要设置
虚拟机要对对象进行必要设置,例如这个对象是哪个类的实例,如何找到类的元数据信息、对象的哈希码、对象的GC分代年龄等。这些信息存放在对象头中。这个看下面的对象内存布局
第五步:执行<init>方法
一般来说,执行new指令后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象就产生了。
2 对象的内存布局
对象在内存中的布局可以分为3块区域:对象头、实例数据和对齐填充。
2.1 对象头
对象头包括两部分信息:
- 第一部分用于存放对象运行时自身的数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分数据的长度在32位和64位的虚拟机中分别是32bit和64bit,官方称为“Mark Word”。对象需要存储的数据很明显会超过32bit或64bit,所以Mark Word被设计为一个非固定的数据结构以便在绩效的空间内存储尽量多的数据信息,他会根据对象的状态复用自己的存储空间。
- 第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪一个类的实例。
2.2 实例数据
实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承的还是在子类定义的,都要记录下来。
2.3 对齐填充
不是必须的,也没什么特别含义,它仅仅起着占位符的作用。