对象创建流程
- 当虚拟机遇到一个字节码new指令,首先检查该指令的参数能否在常量池中定位到一个符号引用,并检查该符号引用所代表的类是否已经被加载、解析、初始化过。如果没有,则必须先进行类加载过程。
- 在类加载检查通过后,接下来虚拟机要为新生对象分配内存空间(对象所需空间的大小在完成类加载后就可以完全确定)。
- 内存分配完成后,虚拟机还需要将分配到的内存空间初始化为0,用于保证对象的实例字段在Java代码中可以不赋初始值就可以使用。(若启用TLAB,则可以在申请到本地线程分配缓冲时就对缓冲内的所有存储单元置零)
- 接下来虚拟机还需要对对象进行必要的设置(如这个对象是哪个类的实例、如何找到类的元数据、对象的哈希码、对象的GC分代年龄等信息),根据虚拟机运行状态的不同,有不同的设置方式。
这一步完成后,在虚拟机看来就完成了对象的创建,但在Java程序看来,这还不是一个可用的Java对象
- 执行构造函数init<>(),对对象进行初始化,构造好对象的其他资源和状态信息,使之成为一个真正可用的对象。
Q&A
如何划分可用空间? 对象创建在虚拟机中是频繁发生的,即使是修改一个指针所指向的位置,在并发情况下也不是线程安全的,应如何解决?
1、划分空间
- 指针碰撞:如果Java堆是严格规整的,已被使用的空间放在一边,空闲的空间被放在另一边,中间存放一个指针指明分界点,这样分配空间就是把指针往空闲空间方向移动所需空间大小的距离,这样既简单又高效。
- 空闲列表:如果Java堆不是严格规整的,已被使用的空间和空闲空间是交错在一起的,那就没有办法采用指针碰撞了,而是使用空闲列表记录下哪些空间是可用的,当分配空间时选择一块足够大的空闲空间分配给对象实例,并更新列表。
由此可见,采用哪种分配方法由Java堆是否规整决定,而Java堆是否规整又由采用的垃圾收集器是否具备空间压缩整理的能力决定。
- 指针碰撞:Serial、ParNew等垃圾收集器具备空间压缩整理能力
- 空闲列表:CMS等采用Sweep清除算法的垃圾收集器
2、线程安全
① 问题描述
当虚拟机在为对象A分配内存,指针还没来得及修改,另一个线程的对象B申请内存使用了旧的指针分配内存。
② 解决方法
- 对分配内存空间的动作进行同步操作。
- 把内存分配的动作按照线程划分到不同的空间,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲,只有本地线程分配缓冲用完了,申请新的缓冲区时才进行同步锁定。