对象的创建
当虚拟机遇到一条new指令时,首先检查这条指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否被加载、解析和初始化过,没有的话必须去执行相应的类加载过程。
类加载检查通过后,Java虚拟机为对象实例分配内存。对象所需的内存大小在类加载完成后就已经确定了。
如何分配内存空间?两种方式:一种是指针碰撞(内存规整),一种是空闲列表(内存不规整)。
仅仅修改一个指针指向的位置,并发情况下也是线程不安全的。两种解决方案:一种是分配动作同步处理,一种是使用线程在不同的空间中处理(每个线程在堆中预先分配一块内存TLBA)。
内存分配完成后,要将对象实例分配到的内存空间初始化为0(不包括对象头)。
接下类Java虚拟机还要对对象做必要的设置,如该对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。这些信息都放在对象头中。
到此于虚拟机的角度对象已经创建完成。
对象的内存布局
在HotSpot虚拟机中,对象的内存布局分为3个区域:对象头、实例数据、对齐填充
对象头
对象头包括两部分信息。第一部分用于存储对象运行时数据如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分是类型指针通过该指针确定对象是哪个类的实例,当然不是所有的虚拟机都会存储该类型指针。
实例数据
实例数据部分是对象真正存储的有效信息即程序中定义的各类型的字段内容。无论是从父类继承还是自身定义的都需要记录下来。存储顺序受虚拟机的分配策略参数和程序中的定义顺序影响。
对齐填充
该部分不必然存在。实例部分没有对齐时通过对象填充来补全。
对象的访问定位
使用对象时是通过栈上的ref数据来操作堆上的具体对象。而ref类型在Java虚拟机规范中也只是指向一个对象的引用,并没有定义如何去定位具体的对象地址,所以对象的访问定位还是通过jvm来实现。两种方式:句柄和直接指针。
句柄
此种方式Java堆会分配一块内存出来作为句柄池,ref类型中存储的就是句柄地址,句柄中包含对象的实例数据地址和对象类型地址。
直接指针
此种方式Java堆对象的布局就该考虑存储访问对象类型的信息,ref类型中存储的就是对象地址。Hotspot使用的是这种方式。
两者方式的优势
句柄方式ref中存储的是句柄地址,对象被移动时(垃圾收集时对象会经常被移动)只修改句柄中的实例数据指针,ref本身不需改变。
直接指针方式节省一次指针定位的时间开销。