仔细看下面的流程图,我们先来获取一个直观的认识,然后再一点一点的进行详细分析!
对象的创建(遇到一条 new 指令时)
- 检查这个指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,先把这个类加载进内存;
- 类加载检查通过后,虚拟机将为新对象分配内存,此时已经可以确定存储这个对象所需的内存大小;
- 在堆中为新对象分配可用内存;
- 将分配到的内存初始化;
- 设置对象头中的数据;
- 此时,从虚拟机的角度看,对象已经创建好了,但从 Java 程序的角度看,对象创建才刚刚开始,构造函数还没有执行。
1、如何在堆中为新对象划分可用的内存?
- 指针碰撞(内存分配规整)
- 用过的内存放一边,没用过的内存放一边,中间用一个指针分隔;
- 分配内存的过程就是将指针向没用过的内存那边移动所需的长度;
- 空闲列表(内存分配不规整)
- 维护一个列表,记录哪些内存块是可用的;
- 分配内存时,从列表上选取一块足够大的空间分给对象,并更新列表上的记录;
2、如何处理多线程创建对象时,划分内存的指针的同步问题?
- 对分配内存空间的动作进行同步处理(CAS);
- 把内存分配动作按照线程划分在不同的空间之中进行;
- 每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB);
- 哪个线程要分配内存就在哪个线程的 TLAB 上分配,TLAB 用完需要分配新的 TLAB 时,才需要同步锁定;
- 通过
-XX:+/-UseTLAB
参数设定是否使用 TLAB。
对象的内存布局
在 HotSpot 虚拟机中,对象在内存中存储布局分为 3 块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding),下面详细讲解各部分内容。
- 对象头:
- 第一部分:存储对象自身运行时的数据,HashCode、GC分代年龄等(Mark Word);
- 第二部分:类型指针,指向它的类元数据的指针,虚拟机通过这个指针来判断这个对象是哪个类的实例(HotSpot 采用的是直接指针的方式访问对象的);
- 如果是个数组对象,对象头中还有一块用于记录数组长度的数据。
- 实例数据:
- 默认分配顺序:longs/doubles、ints、shorts/chars、bytes/booleans、oops (Ordinary Object Pointers),相同宽度的字段会被分配在一起,除了 oops,其他的长度由长到短;
- 默认分配顺序下,父类字段会被分配在子类字段前面。
- 对齐填充:不是必然存在的,没有特别的含义,它仅起到占位符的作用。
注:HotSpot VM要求对象的起始地址必须是8字节的整数倍,所以不够要补齐。