Java虚拟机之‘对象在虚拟机的创建’

        Java程序在运行是,无时无刻都有许多对象被创建,当虚拟机遇上 new 关键字的时候,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析、和初始化过。如果没有,就必须执行相应的的类加载过程。

    

        在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可以完全确定。为对象分配内存的任务等同于把一块确定大小的内存从Java堆中划分出来。

        1. 指针碰撞(Bump The Pointer):假设java堆中的内存是完全规整的,所有使用过的内存都放在一边,没有是用过的内存都放在另一边。如图:

                                    

        中间放着一个指针作为分界点的指示器,那所分配的内存就是仅仅把那个指针向空闲那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”。

        2. 空闲列表(Free List):如果Java堆中的内存是不规整的,以使用的内存和空闲的内存相互交错,那么上一种方法就没有办法实现,虚拟机就必须维护一个列表,记录哪些内存是可用的,在分配的时候从列表找一块足够大的空间划分给对象实例,并更新表上的记录,这中分配方式称为“空闲列表”。

        而Java堆是否规整,由所采用的垃圾收集器是否带有压缩整理功能决定。Serial、ParNew等带有Compact过程的收集器,系统采用的分配系统是指针碰撞;CMS这种基于Mark-Sweep算法收集器,通常采用空闲列表。

        对象的创建在Java虚拟中是非常频繁的行为,如果程序中加入并发操作,由于堆内存是线程共享的,可能会发生两个甚至多个线程同时操作指针。关于这个问题:

        1. 虚拟机采用了CAS(Compare And Swap)操作(详见Unsafe),保证了更新操作的原子性。

        2. 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。通过 -XX:+/-UseTLAB 参数设定。

        内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。

        接下来虚拟机要对对象进行必要的配置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Head)中(详见:对象的内存布局)。根据虚拟机当前的运行状态不同,如是否启用偏向锁等。

        以上工作完成之后,从虚拟机的视角来看,一个新的对象已经产生,但从Java程序的视角来看,对象的创建才刚刚开始——<init>方法还没有执行,所有的字段都还为零。new 指令之后,会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这时一个真正可用的对象才真正产生出来。


参考文献:《深入理解Java虚拟机 JVM高级特性与最佳实践》 -- 周志明

猜你喜欢

转载自blog.csdn.net/u010960184/article/details/79616500