深入理解JVM(二)--对象的创建

  Java是一门面向对象的语言,在Java程序运行的过程中,无时无刻都会有对象被创建出来,在程序语言中,创建对象(例如克隆,反序列化)通常仅仅是一个new关键字,但是在虚拟机中是怎样的呢?本文主要了解一下一个对象(仅代指普通对象,不包含Class类和数组)在虚拟机中的创建过程。

  当虚拟机遇到一条new指令时,首先将去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化过,如果没有,那必须先执行相应的类的加载过程。

  在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象的大小在类加载完毕之后就可以确认,从物理上来讲就是从堆中分配与新生对象大小相等的内存区域来存放新生对象。假设内存是规整的,即已经使用的内存区域是连续的,剩余空闲内存区域也是连续的,此时可以使用一个指针来划分已使用内存区域和可用内存区域,在这种情况下当需要产生新生对象时,只须将该指针向后移动与新生对象大小相等的长度即可,这种分配内存的方式叫“指针碰撞”。当内存并不是规整的时候,就没有办法进行简单的指针碰撞来分配内存了,这时候虚拟机就需要维护一个列表,记录内存上哪些区域是可用的并记录这些区域的大小和起始位置,需要分配内存时,从列表中找到合适的内存位置为新对象分配内存,这种分配方式叫做“空闲列表”。选择哪种内存分配方式由内存区域是否规整决定,而是否规整又取决于该区域采用的垃圾回收算法是否带有整理内存的功能,对于Serial,ParNew等带有compact过程的收集器时,可以使用指针碰撞来分配内存(例如老年代),对于CMS这种mark-sweep算法的收集器,采用空闲列表的方式。

  除了如何划分可用空间之外,还需要考虑在虚拟机中创建对象是非常频繁的行为,即使仅仅移动一个指针的操作,在并发情况下也是不安全的,可能出现正在给A对象分配内存,指针还没来得及修改,对象B又使用了原来的指针位置来分配内存。解决这个问题有两种方式,一是对分配内存空间的动作进行同步处理,实际上虚拟机使用CAS配上失败重试的方式来保证更新操作的原子性。另一种方式是把内存分配的动作按照线程在内存中进行划分,即每个线程在内存中都预先分配一小块区域,称为本地线程分配缓冲(TLAB,Thread Local Allocation Buffer)当这个线程需要创建新生对象的时候,在本线程的内存区域中进行创建,只有TLAB使用完并分配新的TLAB的时候,才需要进行同步处理。虚拟机是否使用TLAB,可以通过 -XX:+/-UseTLAB参数来设定。

  在内存分配完成之后,虚拟机会将新分配的内存空间全部初始化为0值(不包括对象头),如果使用了TLAB,这一过程也可以提前至分配TLAB时执行,这一步保证了对象的实例字段在Java代码中不赋初始值就可以直接使用,程序能访问到这些字段的数据类型对应的零值,如:。

  

  

  接下来,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例,哈希码是多少,对象的分代年龄是多少等信息,这些信息存放在对象头(Object Head)中。这些工作都完成之后,从虚拟机来看,一个对象已经产生了,但是从Java程序的角度看,对象创建才刚刚开始,--<init>方法还没有执行,所有的字段都还为零,执行new指令后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个完整可用的对象才产生出来。

 

  

posted @ 2019-05-10 11:07  有一个小梦想  阅读(368)  评论(0编辑  收藏  举报