20–对象的创建过程
1、对象的创建过程
1、 讨论的对象限于普通Java对象,不包括数组和Class对象等;
2、 虚拟机遇到一条new指令时:过程如下;
1.1、类加载
当虚拟机遇到一条new指令时 ,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过,如果没有,那必须先执行相应的类加载过程。
1.2、内存分配
当已经执行过类加载过程后,会为新对象在Java堆中分配一块大小已经确定(类加载后确定)的内存,具体的内存分配规则有以下两种
1.2.1、指针碰撞(Bump the Pointer)
如果Java堆中的内存是绝对规整的,所有用过的内存放一边,空闲的内存放到一边,中间放着指针为分界点,分配内存就是把指针向空闲的一边挪动一段与对象大小相等的距离。
1.2.2、空闲列表(Free List)
如果Java堆中的内存并不是规整对的,已使用的内存和空间相互交错,虚拟机会将可以用的内存维护到一个列表上,在分配内存时从这个列表中找到一块足够大的空间划给对象。然后更新列表记录(将对应的这个记录在列表中删去)。
1.2.3、扩展
1. 内存分配方式由Java堆是否规则来决定 --》Java堆中是否规整是根据虚拟机所采用的垃圾收集器是否带有压缩整理功能决定的。
2. Serial、ParNew带压缩整理的收集器用指针碰撞。
3. CMS这种基于mark-sweep算法的收集器通常用空闲列表。
1.3、防止并发
在虚拟机上创建对象是非常频繁的行为,所以要做到防止并发,有以下两种方式可实现:
1、 对分配内存空间的动作进行同步处理,实际上JVM采用CAS(CmpareAndSet)配上失败重试的方式保证更新操作的原子性;
2、 把内存分配的动作按照线程划分在不同的空间之中进行,即为每个线程在java堆中预先分配一块小内存,称为本地线程分配缓冲区(ThreadLocalAllocationBuffer,TLAB)分配内存时在线程的TLBA上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定JVM是否使用TLAB可以通过-XX:+UseTLAB参数来设定;
1.4、初始化对象内存空间(换个说法就是:为对象的所有字段附默认值)
内存分配完成后,JVM将分配到的内存空间都初始化为零值(不包括对象头),如果使用TLAB,这一过程可提前到TLAB分配时进行。
1.5、对象头的设置
将对象的类、哈希码、对象的GC分代年龄等信息设置到对象头之中。
1.5.1、执行Java的init方法(换个说法就是:执行构造方法)
设置完对象头后,从JVM的角度来看一个对象已经完成了,但是从java程序的角度来看还没有创建完成呢。此时就需要执行init方法,调用构造方法,这样一个真正可用的对象才算完全的产生出来。
下面代码是HotSpot虚拟机bytecodeInterpreter.cpp中的代码片段(这个解释器实现很少机会实际使用,大部分平台上都使用模板 解释器;当代码通过JIT编译器执行时差异就更大了。不过这段代码用于了解HotSpot的运作过程是没有什么问题的)。
// 确保常量池中存放的是已解释的类
if (!constants->tag_at(index).is_unresolved_klass()) {
// 断言确保是klassOop和instanceKlassOop(这部分下一节介绍)
oop entry = (klassOop) *constants->obj_at_addr(index);
assert(entry->is_klass(), "Should be resolved klass");
klassOop k_entry = (klassOop) entry;
assert(k_entry->klass_part()->oop_is_instance(), "Should be instanceKlass");
instanceKlass* ik = (instanceKlass*) k_entry->klass_part();
// 确保对象所属类型已经经过初始化阶段
if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
// 取对象长度
size_t obj_size = ik->size_helper();
oop result = NULL;
// 记录是否需要将对象所有字段置零值
bool need_zero = !ZeroTLAB;
// 是否在TLAB中分配对象
if (UseTLAB) {
result = (oop) THREAD->tlab().allocate(obj_size);
}
if (result == NULL) {
need_zero = true;
// 直接在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) {
// 如果需要,为对象初始化零值
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);
}
}
}