对象的创建
第一步
当JAVA虚拟机遇到一条new字节码指令时,首先去检查这个指令的参数是否能在常量池中找到它的符号引用,并检查这个符号引用的类是否已经被加载、解析和初始化过,如果没有,则先执行类的加载过程,再创建对象。
第二步
在类加载检查通过后,虚拟机就给新生的对象分配内存。
对象所需的内存大小,在类加载的过程中就已经完全确定了。对象的内存空间分配实际上就是将一小块内存从堆内存划分出来。
分配方式
1、指针碰撞
假设JAVA堆的内存是绝对规整的,就是说被使用的内存划分到一边,空闲的内存划分到另一边,然后中间存放着一个指针作为分界点的指示器。然后分配内存就让指针挪动一段与对象内存大小相等的距离。
2、空闲列表
假设JAVA堆的内存是交错着的。那么就需要JAVA虚拟机维护一个列表,记录哪一块内存是可用的,然后在分配的时候,找一块足够大的内存空间划分给新对象,并更新表上的记录。
总结
当虚拟机采用Serial、ParNew等带压缩功能的垃圾收集器时,使用指针碰撞
的分配方式既简单又高效;当采用CMS这种清除算法的收集器时,理论上(从空闲列表分配的内存空间里面也可以使用指针碰撞的方式)就用空闲列表
来进行内存的分配。
思考的问题
在并发的情况
下,对象的创建是不安全的。比如A线程在给A对象分配完内存,刚要修改指针的指向时,B线程引用了原始的指针来分配内存,这时就会出错。
解决
一种是使用CAS(比较并交换)配上失败重试的方式来保证原子性;另一种是在进行内存分配时,每个线程先在Java堆中预先分配
一小块内存,称为本地线程分配缓冲
(TLAB),用来存放新对象,当这个内存用完,再进行CAS配上失败重试的方式来分配内存。虚拟机是否开启TLAB,可以通过-XX:+/-UseTLAB参数来设定。
第三步
分配完内存之后,Java虚拟机就给分配到的内存空间初始化为零值,如果使用了TLAB,则在进行内存TLAB分配时顺便初始化。这保证了对象的实例不被赋值就可以直接使用,访问到这个实例对应的零值。
最后,new指令之后会接着执行< init >()方法,初始化对象的其他资源和状态信息(因为分配完内存把初始值设置为零值,这并不是程序员想要的结果),这时才真正完成一个对象的创建。
总结
1、首先根据new字节码指令的参数去常量池查找是否有该参数的符号引用,并检查该符号引用对应的类是否被加载、解析、初始化过;
2、如果没有,先进行类的加载再创建对象;
3、对象的创建,需要分配内存,有两种策略:
- 指针碰撞:假定堆内存十分规整,被使用的和未被使用的内存分开来,中间有一个临界标识指针,当给对象分配内存时,指针移动相应的偏移量。
- 空闲列表:堆内存并不规整,这时JVM会维护一个列表,记录空闲的内存,然后找到一个足够大的内存分配给对象。
4、在并发情况下,对象的创建时不安全的,解决方法是可以设置UseTLAB参数来开启本地线程分配缓冲
,在分配内存时,每个线程会在堆上预分配一块内存,用来存放新创建的对象,当内存耗尽时,可以使用CAS和失败重试来分配内存。
5、分配完内存时,给这块内存初始化为零值,如果使用了TLAB,会在分配内存时顺便初始化零值(保证实例不被赋值就可以直接使用)
,接着new指令会执行init()方法,初始化对象的其他资源和状态信息。