天天看点

Understanding the JVM(二)虚拟机对象的创建

对象创建

  • 类加载检查

    遇到new指令时,先检查指令参数是否能在常量池中定位到一个类的符号引用:

    1. 如果能定位到,检查这个符号引用代表的类是否已被加载、解析和初始化过;
    2. 如果不能定位到,或没有检查到,就先执行相应的类加载过程;
  • 为对象分配内存

    对象所需内存的大小在类加载完成后便完全确定(JVM可以通过普通Java对象的类元数据信息确定对象大小),为对象分配内存相当于把一块确定大小的内存从Java堆里划分出来。

    分配方式:

    1. 指针碰撞

      如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器;分配内存只需向空闲那边移动指针,这种分配方式称为”指针碰撞”(Bump the Pointer);

    2. 空闲列表

      如果Java堆不是规整的:用过的和空闲的内存相互交错, 需要维护一个列表,记录哪些内存可用。 分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为”空闲列表”(Free List);

    3. 决定因素

      Java堆是否规整由JVM采用的垃圾收集器是否带有压缩功能决定的; 所以,使用Serial、ParNew等带Compact过程的收集器时,JVM采用指针碰撞方式分配内存;而使用CMS这种基于标记-清除(Mark-Sweep)算法的收集器时,采用空闲列表方式;

      后面了解垃圾收集时应注意这里的内容。

  • 线程安全问题

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

    1. 同步处理

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

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

      CAS:有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做;

    2. 本地线程分配缓冲区

      把分配内存的动作按照线程划分在不同的空间中进行:

      在每个线程在Java堆预先分配一小块内存,称为本地线程分配缓冲区(Thread Local Allocation Buffer,TLAB)。 哪个线程需要分配内存就从哪个线程的TLAB上分配,只有TLAB用完需要分配新的TLAB时,才需要同步处理;

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

  • 对象内存初始化为零

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

    接下来虚拟机对对象进行必要的设置,包括设置对象头信息,包括类元数据引用、对象的哈希码、对象的GC分代年龄等。

  • 执行对象实例方法

    此时从程序员的视角来看,对象的创建才刚刚开始。此时没有执行

    <init>

    方法,所有的字段都是初始零值。

    <init>

    该方法把对象(实例变量)按照程序中定义的初始赋值进行初始化;