从代码 Clazz instance = new Clazz;
编译成class中的new指令,
虚拟机在遇到这个new的指令的时候,具体会去做什么呢?
大概分为了如下几个大的步骤:
* 检验
* 为对象分配内存
* 考虑线程安全问题
而每一个大的步骤下还有很多细节步骤:
检验:
- 检查这个new指令是否能在常量池中定位到一个类的引用
- 接着检查这个类的引用是否已经被vm加载并且解析初始化过
如果上述的步骤表明一个类尚未被vm加载过,那么执行加载类的步骤,也就是开始为类对象分配内存.
分配内存:
- 一旦一个类成功的加载入虚拟机,这个类所占据的内存大小也同时给固定下来了。(疑问: 一个类含有StringBuilder之类的可动态扩展内存大小的对象呢? 在runTime期间动态改变自己所占据的内存大小,a: 默认的capacity 16字符长,当容积不够的话会重新从堆内存中申请一块原容积*2大小的内存区域,而内存边界指针只需要拨动对应内存大小的位置即可),但是内存如何从堆内存区域 划分出来的呢? 根据堆内存是否完整来划分的:
- 内存根据已经使用和空闲状态分为了两个区域,也就是连续状态,而其中空闲与使用过的边界就是一个指针来记录着这个边界的内存地址。只要对象申请了指定大小的内存区域,那么这个指针只需要在空闲内存偏移指定大小的地址空间即可完成内存的分配。
- 另外一种就是非理想状态也是实际状态的了:内存并非是连续的,所以虚拟机需要维护一个列表记录了哪些可用的内存地址区域,在分配内存的时候更新此列表即可。当然这样是麻烦了很多
- 总结就是内存的分配方式是根据内存区域是否连续而决定,而内存区域是否连续的根本原因在于垃圾回收器是否采用了内存压缩整理算法(Serial之类的算法采用了压缩整理算法所以内存的分配是采用指针碰撞式,而类似Mark-Sweep算法收集器采用的是空闲列表式来处理内存的分配)。
在解决了内存分配的同时需要考虑的一个难点就是,在并发环境下:虚拟机在频繁的对象内存分配的时候如何保证线程的安全呢?
考虑并发情況下线程安全问题,有两种方案:
- 对内存分配这一操作进行同步(也就是保证内存分配操作的原子性)
- 每一个线程在堆内存预先分配了一小块内存称之为ThreadLocalAllocationBuffer,这个内存用于记录本线程所执行的内存分配操作,当ThreadLocalAllocationBuffer内存用完分配新的 ThreadLocalAllocationBuffer 时候才考虑同步锁定内存区域。
当类对象成功的allocate到了指定大小的内存区域,虚拟机会把分配给类对象的这块区域初始化一下,初始化的内容就是把这块区域的内容全部置为0.这一步骤保证了一些对象可以在不赋予初始值就可以访问到对应数据类型的零值。
紧接着就是Object Header的设置了,包括对象的类,对象的hashcode,GC的年代信息之类的。
以上整个流程只是强调VM中完成了一个类的初始化
而已,在Java程序看来却是啥都没发生过
,因为init还没执行呢,所有的变量槽位都还是0值呢,只有init执行完成一个真正意义上的Class Object才初始化完成。