一、Java对象的组成
1.对象头
运行时数据
存储对象运行时的数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据官方成为“Mark Word”,它的长度在32位和64位的虚拟机中分别是32bit和64bit。32bit的HotSpot虚拟机中,当对象未被锁定时的组成如下表:
类型指针
对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
2.实例数据
对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容
3.对齐填充
这部分不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是说对象必须是8字节的整数倍,因此当实例数据不是8字节的倍数的时候就需要占位符来填充。
二、对象创建的流程
- 虚拟机接收到new指令
- 检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且这个类是否已经被加载、解析和初始化过,如果没有则先执行类的加载过程
- 虚拟机为新生对象分配内存空间
- 将对象中的实例数据都初始化为零值
- 设置对象的header部分的数据
- 设置对象的实例数据(执行构造器)
内存分配
对象大小在类加载完成后就可以完全确定。内存分配有两种分配方式:指针碰撞和空闲列表。
- 指针碰撞:前提是堆内存中的空闲空间十分的规整,使用与未使用的空间全部为连续,只需移动一下指针就可以了
- 空闲列表:针对堆内存中的空间零散的存在,虚拟机维护着一个列表,记录着哪里被分配了,哪里还空闲。
三、Java对象的访问方式
一般来说,一个Java的引用访问涉及到3个内存区域:JVM栈,堆,方法区。以最简单的本地变量引用:Object obj = new Object()为例:
- Object obj表示一个本地引用,存储在JVM栈的本地变量表中,表示一个reference类型数据;
- new Object()作为实例对象数据存储在堆中;
- 堆中还记录了Object类的类型信息(接口、方法、field、对象类型等)的地址,这些地址所执行的数据存储在方法区中;
在Java虚拟机规范中,对于通过reference类型引用访问具体对象的方式并未做规定,目前主流的实现方式主要有两种:
1.通过句柄访问
通过句柄访问的实现方式中,JVM堆中会专门有一块区域用来作为句柄池,存储相关句柄所执行的实例数据地址(包括在堆中地址和在方法区中的地址)。这种实现方法由于用句柄表示地址,因此十分稳定。
2.通过直接指针访问
通过直接指针访问的方式中,reference中存储的就是对象在堆中的实际地址,在堆中存储的对象信息中包含了在方法区中的相应类型数据。这种方法最大的优势是速度快,在HotSpot虚拟机中用的就是这种方式。