一、虚拟机中Java对象的创建
参考:《深入理解Java虚拟机》-jvm高级特性与最佳实现(周志明著)
语言层面上,创建Java对象通常仅仅是一个new关键字而已。
在虚拟遇到new指令时:
1、首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个类的符号引用代表的类是否已经加载,解析和初始化过。如果没有,则必须执行类加载过程
2、接下来为新生对象分配内存,对象所需的大小在类加载后便可以完全确定。
3、内存分配的方式有两种-指针碰撞(Bump the Pointer)和空闲列表(Free List),具体选择哪种分配方式是由Java堆是否规整决定的,而Java堆是否规整,则又是由所采用的垃圾收集器是否带有压缩整理功能决定的。
4、分配内存时需要考虑并发情况下线程的安全问题。有两种方案可以避免,一种是对分配内存的动作进行同步处理——实际上虚拟机采用CAS配上失败重试的方式就可以保证更新操作的原子性;另外一种是把内存的分配动作按照线程划分在不同的空间进行,也就是每个线程先分配一小块内存,称为本地线程缓冲(Thread Local Allocation Buffer,TLAB),哪个线程需要分配内存时,从TLAB上进行分配,当TLAB 用完时,才需要同步锁定。
5、内存分配完成之后,虚拟机需要将分配的内存空间初始化零值
6、接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,GC分代年龄等,这些信息放在对象头(Object Header)中
当完成以上动作之后,从虚拟机的视角来看,一个对象产生了,但是从Java程序的角度来看,对象的创建才刚刚开始——<init>方法还没有执行。一般来说,执行完new指令后会接着执行<init>方法,把对象按照程序员的意愿进行初始化。这样一个真正可用的对象才算完全产生出来。
二、对象的内存布局
在HotSpot虚拟机中,对象在内存中的布局可以分为3块区域:对象头(Header),实例数据(Instance Data),对齐填充(Padding)。
在HotSpot虚拟机中,对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码值,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳。这部分数据长度在32位和64位虚拟机中分别为32bit和64bit,官方称它为Mark Word,Mark Word被设计成一个非固定数据结构,以便在极小的空间内存储更多的信息,对象头的另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象并不一定要经过对象本身。如果Java对象是一个数组,那对象头中还必须有一块用于记录数组长度的数据。
实例数据是对象真正存储的有效信息,也是在程序代码中所定义的各种类型字段内容
第三部分对齐填充并不是必然存在的,也没有特别的含义,他仅仅起着占位的作用。由于HotSpot虚拟自动内存管理系统要求对象的地址必须是8字节的整数倍。当对象实例数据没有对齐时,就需要通过对齐填充来补全。
三、对象的访问定位
目前主流的访问方式有使用句柄和直接指针两种。
如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息
如果使用直接指针访问,那么Java堆中对象的布局中就必须考虑如何放置访问类型数据的相关信息。而reference中存储的直接就是对象的地址。(就HotSpot虚拟机而言,使用的是这种方式进行访问对象)