- Java对象的内存布局?
- 对象的访问?
- new对象的过程?
一、Java对象的内存布局
对象的内存布局对象的创建过程就是在堆上分配实例对象内容空间的过程,对象在堆中的内存布局如何呢?
对象头
包含两部分:
- 第一部分用于存储自身运行时的数据。例如GC标志位、哈希码、锁状态等信息。
- 第二部分是指向方法区的类型指针。用于确定对象是哪个Class的实例。
实例数据
对象存储的真正的有效信息,存储着自身的属性数据信息和从父类继承下来的实例字段。
对齐填充
虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。HotSpot VM的自动内存管理要求对象起始地址必须是8字节的整数倍。对象头本身是8的倍数,当对象的实例变量数据不是8的倍数,便需要填充数据来保证8字节的对齐。
二、对象的访问
创建对象后,是通过栈上的reference数据来操作堆上的该具体对象。虚拟机规范中,reference类型数据为一个指向对象的引用,但该引用如何去定位、访问堆中的对象的具体位置则是由虚拟机实现而定的。
有两种常用的对象访问方式。
句柄访问
堆中划分出一块内存作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
这种访问方式的优势是,reference中存储的是稳定的句柄地址,在对象被移动后(比如垃圾收集时),只会改变句柄中的实例数据指针,reference本身不需要修改。
句柄访问
直接指针
直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在Java中非常频繁。Sun HotSpot虚拟机采用直接指针访问对象。
直接指针
三、new对象的过程
1.new指令发出。
即执行类似
Object o = new Object()
的语句。
2.检查对应的 Class 是否已经初始化。(如果没有,执行类的加载过程)
3.分配内存,检查堆是否规整。
在堆中分配内存,对象的大小在类加载完成后就完全确定。
依据java堆是否规整。内存分配的有两种方式。
指针碰撞 假设堆中内存绝对规整,用过的内存放一边,没用过的放一边,中间用一个指针作为分界点的指示器。分配内存仅仅是将指针向空闲空间那边挪动一段与对象大小相等的距离。
空闲列表 假设堆中内存不规整,已使用的和空闲的内存相互交错,此时,虚拟机维护一个列表,用来记录哪些内存可用。在分配内存时,从列表中找到一块足够大的空间划分给该对象实例,并更新列表上的记录。
如何保证内存分配的线程安全?即多个线程new对象时,如何保证这些线程使用的内存不存在重叠的情况?
对分配内存空间的动作进行同步处理 采用CAS+失败重试的方式保证更新操作的原子性。
把内存分配的动作按照线程划分在不同的空间上 即每个线程在堆中预先分配一小块内存区域,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有当TLAB用完并分配新的TLAB时,才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数设定。
4.内存空间初始化为零值(不包括对象头)
将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在Java代码中可以不赋初值就可以直接使用,程序能够访问到这些字段的数据类型所对应的零值。
5.对对象进行重要的配置
例如该对象是哪个类的实例、如何才能找到类的元数据信息、对象的Hash码、对象的GC分代年龄信息等。这些信息存放在对象的对象头之中。
6.执行 < init > 方法
执行后,一个真正可用的对象完全产生。
网友评论