1. 对象的创建
1.1 类加载检查
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。如果没有,就先执行相应的类加载过程。
1.2 分配内存
从Java堆中划分出一块确定大小的内存分配给新生对象。
垃圾收集器是否带有压缩整理功能决定Java堆是否规整。而Java堆是否规整决定了内存的分配方式。
- “指针碰撞”:若Java堆中内存绝对规整,所有用过的内存放一边,空闲的内存放在另外一边,中间放着一个指针作为分界点的指示器,那所分配的内存就仅仅是把那个指针往空闲空间那边挪动一段与对象大小相等的距离。
- “空闲列表”:若Java堆中的内存并不规整,则虚拟机维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
并发下的线程安全
- 对分配内存空间的动作进行同步处理。采用CAS配上失败重试的方式保证更新操作的原子性。
- 把内存分配的动作按照线程划分在不同的空间之中进行。每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配。只有在TLAB用完并分配新的TLAB时,才需要同步锁定。
1.3 初始化
虚拟机将分配到的内存空间都初始化为零值(不包括对象头)。
2. 对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
2.1 对象头
对象头包含两部分信息:
- 存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
- 类型指针,即对象指向其类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
3. 对象的访问定位
Java程序需要通过栈上的reference数据来操作堆上的具体对象。
3.1 使用句柄访问
Java堆会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了实例数据与类型数据各自的具体信息。
reference->句柄池->实例池(对象实例数据)+ 方法区(对象类型数据)
优势:reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象时非常普遍的行为)时,只会改变句柄中的实例数据指针,reference本身不需要修改。
3.2 使用直接指针访问
在Java堆对象中放置访问类型数据的相关信息。而reference中存储的直接就是对象地址。
reference->对象实例数据->方法区(对象类型数据)
优势:速度快,节省了一次指针定位的时间开销。