本文讨论的是Java对象在jvm中创建的过程(不包括数组、Class对象的创建)
-
类的加载、连接、初始化
在语言层面上,我们创建对象需要new关键词来创建对象,当JVM遇到这个指令时,首先检查这个指令能否在常量池中定位到这个类的符号引用,并且要检查这个符号引用代表的类是否已经被加载、连接(验证、准备、解析)、初始化过,如果没有那么就去执行类加载过程,详见:类加载机制,这里不再赘述。
-
新生对象分配内存的方式
2.1 对象内存布局
要分配新生对象内存,那么先得知道它所需内存大小,在HotSpot虚拟机中,对象包含三个部分:对象头、实例数据、对齐填充
对象头:包含两部分:一部分存储哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID等,在32位VM中对应32比特位(25位哈希码,4位分代年龄,2位锁标志,1位固定为0,至于其他信息则会在锁标志位等其他比特位可查看出),64VM对应64比特。另一部分是类型指针,对象指向它的类型元数据指针,用来确定这个指针是哪个类的实例。
实例数据:包含其中所有有效信息(各种类型字段内容),包括自己和父类继承下来的字段
对齐填充:占位符的作用,无其他作用,(任何对象的大小都必须是8字节的整数倍)
2.2 分配内存方式
假设Java堆内存是绝对规整的(无内存碎片,所有使用过的内存在一起,所有没被使用过的内存在一起),中间临界处有一个指针,分配对象时,仅仅把指针向空闲方向移动此对象大小相等的距离,这种分配方式称为指针碰撞;
而如果内存不规整,那么vm必须维护一个空闲内存列表,分配的时候找到一块足够大的空间分配给对象实例,并更新列表,这种分配方式成为空闲列表。所以选择那种分配方式由垃圾收集器所决定(垃圾收集器采用的收集算法不同,所对应算法是否会产生内存碎片也不同,Serial,ParNew为指针碰撞,高效简单,而CMS这种则是采用空闲列表较为复杂的方式)。
-
并发问题
创建对象是非常频繁的行为,仅仅修改一个指针,在并发场景下也不是线程安全的(正在给对象A分配内存,还没来得及修改指针,B就用了原来的指针来分配内存)。VM则采用CAS配上失败重试的方式保证其原子性,或者采用TLAB(Thread Local Allocation Buffer,本地线程分配缓冲),先在缓存区分配内存,缓冲区满了之后,分配新的缓冲区时同步锁定。
- VM将分配到的内存空间都初始化为0(0,null,false等)
- VM对对象进行一些设置,
- 构造函数