1)java虚拟机中对象的创建

  我们了解虚拟机内存划分的人,都知道对象的内存分配几乎都是在堆上的,这一点在java虚拟机规范中的描述是:所有的对象实例以及数组都会在堆上分配(但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配,标量替换优化技术将会导致一些变化,擦扯远了,所以所有的对象实例都在堆上分配就不是那么绝对了)

下面说虚拟机中对象的创建几个步骤

  1. 类加载检查
      虚拟机遇到new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被装载、解析和初始化过。如果没有那必须先执行相应的类的加载过程。
  2. 为对象分配内存
      当类加载成功后,类的对象的大小是确定了的。对象内存的划分等同于在堆里划分出一块指定大小的内存。
      假设内存是规整的,所有用过的内存放在一边,空闲的放在另一边,中间分界点有个指针,那分配内存就是把指针向空闲区域方向挪动一段于对象大小相等的长度,这种分配方式叫做“指针碰撞”(Bump the Pointer)。
      如果内存不是规整的,那么就需要一个表来记录,记录哪些内存是占用的,哪些是空闲的,那分配内存就是在表里找到一块足够大的空间分配给对象实例,并更新这个表的记录,这种分配方式叫做“空闲列表”(Free List)。
      选择哪种分配方式与堆是否规整决定,而java堆是否规整又和垃圾收集器是否有压缩整理功能决定。因此,在使用Serial、ParNew等带有Compact过程的收集器时,采用的分配方式是指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,采用的是空闲列表。
      除了如何划分内存之外还有一个是我们需要考虑的问题,因为java堆是线程共享的,那么多个线程同时操作堆上的内存就会有问题,比如正在给a对象分配内存还没来得及移动指针(或者是没有修改空闲列表的记录),这时对象b使用原来的指针来分配内存,就会产生问题。解决这个问题有两种方式,一种是对分配内存的动作进行同步处理(实际上虚拟机采用CAS配上错误重试来保证分配内存的原子性);另一种是采用线程间不共享的内存来分配,每个线程预先在java堆中分配一块内存,成为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB )。哪个线程分配内存就在该线程的TLAB上分配,只有在TLAB用完需要分配新的TLAB时,才需要同步锁定。注:虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来设定。
  3. 内存初始化
      内存分配完后,需要把将分配到的内存空间初始化为零值(不包换对象头),如果使用TLAB这一过程也可以提前至TLAB分配时进行。这一步操作保证了对象实例字段在java代码中可以不赋初值就使用,程序能访问到这些字段的数据类型所对应的零值。
  4. 对象头必要设置
      接下来虚拟机要对对象进行必要的设置,比如该对象是哪个类的实例、如何才能找到类的元数据信息、对象的hash码、对象的gc分代年龄等信息。这些信息均放在对象的对象头(Object Header)中。根据虚拟机当前的运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置

总结
以上步骤完成后,从虚拟机的角度来看,一个新对象诞生了,但从java程序角度看,一切才刚刚开始——init方法还没有执行,所有字段的数据类型都是对应的零值。所以一般来说执行new指令后会接着执行init方法,把对象按照程序猿的意愿进行初始化,这样一个真正可以使用的对象才算完成。

下面的代码是HotPot虚拟机bytecodeInterpreter.cpp的代码片段

u2 index = Bytes::get_Java_u2(pc+1);
ConstantPool* constants = istate->method()->constants();
// 确保常量池中是已经解释的类
if (!constants->tag_at(index).is_unresolved_klass()) {
  // Make sure klass is initialized and doesn't have a finalizer
  // 确保类已经初始化
  Klass* entry = constants->slot_at(index).get_klass();
  assert(entry->is_klass(), "Should be resolved klass");
  Klass* k_entry = (Klass*) entry;
  assert(k_entry->oop_is_instance(), "Should be InstanceKlass");
  InstanceKlass* ik = (InstanceKlass*) k_entry;
  // 确保对象内存已经初始化
  if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
    // 取对象长度
    size_t obj_size = ik->size_helper();
    oop result = NULL;
    // If the TLAB isn't pre-zeroed then we'll have to do it
    // 如果TLAB没有预先初始化那么need_zero为true后边会进行初始化
    bool need_zero = !ZeroTLAB;
    // 如果虚拟机启用TLAB,那么在TLAB中分配对象
    if (UseTLAB) {
      result = (oop) THREAD->tlab().allocate(obj_size);
    }
    if (result == NULL) {
      need_zero = true;
      // Try allocate in shared eden
      // 尝试在eden中分配对象
retry:
      HeapWord* compare_to = *Universe::heap()->top_addr();
      HeapWord* new_top = compare_to + obj_size;
      /*cmpxchg是x86中的CAS指令,这里是一个C++方法通过CAS方式分配空间,如果并发失败,转到retry中重试直至成功分配为止*/
      if (new_top <= *Universe::heap()->end_addr()) {
        if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
          goto retry;
        }
        result = (oop) compare_to;
      }
    }
    if (result != NULL) {
      // Initialize object (if nonzero size and need) and then the header
     // 如果需要,则为对象初始化零值
      if (need_zero ) {
        HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;
        obj_size -= sizeof(oopDesc) / oopSize;
        if (obj_size > 0 ) {
          memset(to_zero, 0, obj_size * HeapWordSize);
        }
      }
      // 根据是否启用偏向锁来设置对象头信息
      if (UseBiasedLocking) {
        result->set_mark(ik->prototype_header());
      } else {
        result->set_mark(markOopDesc::prototype());
      }
      result->set_klass_gap(0);
      result->set_klass(k_entry);
     // 将对象引用入栈,继续执行下一条指令
      SET_STACK_OBJECT(result, 0);
      UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
    }
  }
}

相关文章:
java虚拟机中对象的创建
java虚拟机中对象的内存布局
java虚拟机中对象的定位

你可能感兴趣的:(1)java虚拟机中对象的创建)