Java程序员都知道如何创建对象,不就是一个 Person person = new Person()
的语句就解决了么?然而,我们只知道new,却对于底层如何实现对象的创建、如何存储到内存中去、又如何被访问的知之甚少。
new指令
时,首先检查这个指令的参数是否能在常量池中定位到一个类的 符号引用
,且检查该符号引用代表的 类是否已被加载、解析和初始化过
。若没有,需先进行相应的类加载过程。 分配内存
。(对象在内存中所需要的大小在类加载完成后就确定了) 初始化为零值
(不包括对象头)。保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,可以访问对应的零值。(对应准备阶段) 对象头的设置
)。如这个对象是哪个类的实例、如何找到类的元数据信息、对象哈希码、对象的GC分代年龄等信息。 <init>
方法,将对象在程序中进行初始化。 为对象分配空间就是从 Java堆
中划分出一块确定大小的内存给新生对象,考虑符合划分可用空间的两种方式: “指针碰撞”和“空闲列表”
指针作为分界点的指示器
,所分配内存仅仅是把那个 指针向空闲空间那边挪动一段与对象大小相等的距离
。在使用 Serial、ParNew收集器时
等带有 Compact过程
时,系统分配算法是指针碰撞。 CMS收集器
时,就是采用的空闲里列表,CMS是基于 Mark-Sweep算法(标记-清除)
的收集器。 Java对象创建在程序中是非常常见的,所以在VM中对象创建是非常频繁,容易出现 多线程并发安全问题
:如程序中创建对象A和对象B,底层VM给A对象分配内存,指针没来及修改,对象B同时使用原来的指针分配内存。 解决方案有两种: 同步处理和本地线程分配缓冲
CAS配上失败重试
的方式保证更新操作的 原子性
; 按照线程划分
在 不同的空间
之中进行,即 每个线程在Java堆中预先分配一小块内存
,即为TLAB,哪个线程要分配内存,就在哪个线程的TLAB上分配,只有用完后并分配新的TLAB,才需要同步锁定。通过 -XX:+/-UseTLAB
参数设定是否需要使用TLAB。 Java对象在内存存储的布局分为3块: 对象头、实例数据和对齐填充
。
对象头(Header)分为两部分: 用于存储对象自身的运行时数据和类型指针
。
Mark Word
,用于存储对象自身的运行时数据包括:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希码、GC分代年龄 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级的指针 | 10 | 膨胀(重量级锁定) |
空,不需要记录信息 | 11 | GC标志 |
偏向线程ID、偏向时间戳、对象分代年龄 | 01 | 可偏向 |
Mark Word是一个 非固定
的数据结构,在极小的空间内存储尽量多的数据,会根据对象的状态 复用
自己的存储空间,如在32位HotSpot VM中,若对象处于未锁定状态,Mark Word的32bit空间中25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,即 32(存储空间)=25(哈希码)+4(分代年龄)+2(锁标志位)+1(固定0)
即 对象指向它的类元数据的指针
,虚拟机通过这个指针来确定对象是哪个类的实例,但是并非查找对象的元数据就一定要通过对象本身,也只是适用于普通对象, 普通Java对象可以通过元数据信息
可以确定Java对象的大小。 不适用的Java对象,如Java数组对象
的对象头中必须有一块能保持记录数组长度的数据,因为从数组元数据中无法确定数组的大小。
实例数据(Instance Data)是 对象真正存储的有效信息
,也是程序代码中定义的 各种类型的字段内容
。这部分存储顺序会受到 VM分配策略参数
和 字段在Java源码中定义顺序
的影响。
VM默认分配策略
HotSpot默认分配策略为longs/doubles、ints、shorts/chars、bytes/nooleans、oops,相同宽度的字段会被分配到一起,在父类中定义的变量会出现在子类之前。
对齐填充(Padding)是 非必要
的,只是起着 占位符的作用
。VM自动内存管理系统要求对象起始地址(对象大小)必须是 8字节的整数倍
,对象头都是8字节的整数倍,而实例数据部分若没有8字节的整数倍,可以通过对齐填充进行补全。
Java程序通过栈上的reference数据类操作堆上的具体对象(栈中的局部变量表存储了对象名的变量,堆中存储了对象的具体地址)。主流的对象访问定位方式有两种: 使用句柄和直接指针
。
使用句柄访问对象,Java堆中会划分出一块内存作为 句柄池
, reference中存储
的就是 对象的句柄地址
,句柄中包含了对象实例数据与类型数据各自的具体地址信息。
使用直接指针访问,Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference中存储
的直接就是 对象地址
。(Sun HotSport VM的使用方式)