JVM

JVM-11丨Java对象创建过程

Posted by jiefang on November 28, 2019

Java对象创建过程

  1. 虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那必须先执行相应的类加载过程;
  2. 在类加载检查通过后,虚拟机将为新生对象分配内存,对象所需内存的大小在类加载完成后便可以确定;
  3. 内存分配完成后,虚拟机需要对象实例变量都初始化为零值(不包括对象头),若使用TLAB,这一工作过程也可提前至TLAB分配时进行。保证了对象的实例字段在Java代码中可以不赋初始值就可以直接使用,程序访问字段的数据类型所对应的零值;
  4. 虚拟机对对象头进行必要的设置(对象哈希码,GC分代年龄、偏向锁等信息)存放在对象的对象头(Object Header)之中;
  5. 执行init方法,把对象初始化,产生一个真正可用的对象;

对象内存分配

对象所需内存的大小在类加载完成后便完全确定(JVM可以通过普通Java对象的类元数据信息确定对象大小);

为对象分配内存相当于把一块确定大小的内存从Java堆里划分出来;

分配方式

指针碰撞
  • 如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器;
  • 分配内存只需向空闲那边移动指针,这种分配方式称为”指针碰撞”(Bump the Pointer);
空闲列表
  • 如果Java堆不是规整的:用过的和空闲的内存相互交错;
  • 需要维护一个列表,记录哪些内存可用;
  • 分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为”空闲列表”(Free List);

Java堆是否规整由JVM采用的垃圾收集器是否带有压缩功能决定的;

所以,使用SerialParNew等带Compact过程的收集器时,JVM采用指针碰撞方式分配内存;而使用CMS这种基于标记-清除(Mark-Sweep)算法的收集器时,采用空闲列表方式;

线程安全问题

并发时,上面两种方式分配内存的操作都不是线程安全的,有两种解决方案:

同步处理

对分配内存的动作进行同步处理:

JVM采用CAS(Compare and Swap)机制加上失败重试的方式,保证更新操作的原子性;

TLAB(本地线程分配缓冲区)

TLAB(本地线程分配缓冲区)指的是:在每个线程在Java堆预先分配一小块内存,称为本地线程分配缓冲区(Thread Local Allocation Buffer,TLAB);

  • 哪个线程需要分配内存就从哪个线程的TLAB上分配;
  • 只有TLAB用完需要分配新的TLAB时,才需要同步处理;

JVM通过"-XX:+/-UseTLAB"指定是否使用TLAB;

对象实例变量赋初值

对象实例变量初始化为零,但不包括对象头; 如果使用TLAB,初始化提前至分配TLAB时; 这保证了程序中对象(及实例变量)不显式初始赋零值,程序也能访问到零值;