Java对象的创建过程
在虚拟机的运行时数据区中,大家都知道有一个运行时常量池,那么常量池中都包含什么内容呢?
先来看一下:
开始正文:
1、类加载检查:
当虚拟机遇到一条字节码new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
2、分配内存:
在类加载检查通过后,接下来虚拟机将会为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务实际上等同于把一块确定大小的内存块从Java堆中划分出来。
内存分配的方式有两种:指针碰撞和空闲列表。
选择那种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器的算法是 “标记-清除”,还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的。
除如何划分可用空间之外,还有另外一个需要考虑的问题:那就是线程安全。对象创建是很频繁的行为,即使仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。
解决这个问题有两种方案:
- CAS+失败重试:虚拟机是采用CAS配上失败重试的方式保证更新操作的原子性。CAS是乐观锁的一种实现方式,每次不加锁去执行操作,如果有冲突失败就重试,直到成功为止。
- TLAB(本地线程分配缓冲):把内存分配的动作按照按照线程划分在不同的空间进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲,那个线程要分配内存,就在哪个线程的本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。虚拟机是否使用TLAB,可以通过-XX:+/-UserTLAB参数来设定。
3、初始化零值:
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4、设置对象头:
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
5、执行init方法:
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,init 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 init 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全被构造出来。
参考书籍:《深入理解Java虚拟机》