Java虚拟机学习记录(三)-对象创建的过程

在Java程序运行时几乎每时每刻都有对象在被创建出来,从语言层面上看,只是new了一个对象,但是在虚拟机中这个对象的创建过程时比较复杂的(这里的对象仅适用于普通的Java对象,不包括数据和Class对象)。我把这其中的步骤总结为下面几步

1.类加载检查
当虚拟机接受到new指令时,首先去查常量池中能否定位到这个类的符号引用,并且检查这个符号引用的类是否已经被加载、解析和初始化过。如果没有那就必须执行类的加载过程。简单来说,就是虚拟机中有没有这个类的信息,如果没有就得去加载。

2.为对象分配内存
对象需要的内存在类加载完成之后就已经完全确定了,为对象分配内存的任务等同于在Java堆上划分出一块确定大小的内存。

这个划分内存的动作有两种情况。

如果Java堆中的内存时规整的,使用过的放一边,未使用的放另一边,中间用一个指针作为分界点的指示器。那么分配内存的动作就是将指针向未使用的那一边移动所需要的内存大小。这种分配方式叫做指针碰撞(Bump the Pointer)。

如果Java堆中的内存时不规整的,这种情况很明显不适用于指针碰撞,一般这种情况下Java虚拟机会维持一张表来记录内存的使用情况,哪些内存使用了,哪些内存时空闲的都会记录好,需要分配内存时在表中查找到合适的内存区域分配,然后更新这张表即可。这张表被称为空闲列表(Free List)

使用指针碰撞还是空闲列表由Java堆是否规整决定,而Java堆是否规整则由Java虚拟机的GC算法是否带有压缩整理功能决定。

在并发环境下,简单的修改指针指向的内存位置并不安全,因为A对象分配内存的时候,指针还没有移动的同时,B对象也要开始分配内存,因此使用的还是未发生改变的指针。解决方案有两种,一种是对分配内存空间的动作作同步处理————实际上虚拟机采用CAS配上失败重试的方式来保证操作的原子性;另一种是把内存分配动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程分配内存,就在那个线程的TLAB上分配,只有TLAB分配完并分配新的TLAB时,才需要同步锁定。

3.内存初始化
在分配完内存后,虚拟机需要将分配到的内存空间初始化为0(不包括对象头),如果使用TLAB,那么在TLAB分配时就可以完成这一步骤。这一步骤保证了对象中的字段不初始化就能直接使用。

package test;

public class NoInitInt {
    private int noInitInteger;

    public static void main(String[] args){
        int i = 0;
        System.out.println(i);
    }

    public int getNoInitInteger() {
        return noInitInteger;
    }

    public void setNoInitInteger(int noInitInteger) {
        NoInitInt noInitInt = new NoInitInt();
        System.out.println(noInitInt.getNoInitInteger());
    }
}

如上程序所示,输出为0

4.对象设置

Java虚拟机需要设置对象是哪个类的实例,如果才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。这些信息被存放在对象的对象头中(Object Header)

5.初始化

经过上面的步骤,从Java虚拟机的角度一个新的对象已经生成了,但是从Java程序员的角度,这个对象还差一步,就是对象的初始化

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据