[KANMARS原创] - (三) 从JVM源码来理解NEW指令是如何在堆中分配内存和实例化对象的
由于JVM是一个多平台的可移植语言,人力无法全部精通,因此本文描述的是:基于X86_64系列CPU、Linux\Unix类操作系统、GCC编译器的指令序列。
本文描述JVM中对象的分配过程
我们需要从java开始看对象是如何分配的。
1、JAVA中对象的分配,例如HashMap m = new HashMap();
通过javac编译成字节码后是如下效果
0: new #2; //class java/util/HashMap
3: dup
4: invokespecial #3; //Method java/util/HashMap.”<init>”:()V
7: astore_1
翻译为中文是:
0 new新建一个内存区域,区域的类型为class java/util/HashMap,
将引用值压入栈顶
3 复制0申请出的内存区域的指针并压入栈顶
4 执行特殊方法 构造函数Method java/util/HashMap.”<init>”:()V
执行完成后,令3压入栈顶的指针弹出
7 将0压入栈顶的引用,存入局部变量表1中
总结:
java语言中,对象的分配是通过创建对象内存,和调用对象的构造方法两个过程来实现的。
因此,我们需要关注new字节码和invokespecial字节码的作用方式
2、new关键字的作用机制
JVM的解释器是采用一种叫做模版解释器的技术,所谓template的技术,将指令序列的顶部进行分析,进而总结成“模版”,继而汇编成为汇编语言,的执行技术。
解释器的模版表位于
\hotspot\src\share\vm\interpreter\templateTable.hpp
\hotspot\src\share\vm\interpreter\templateTable.cpp
\hotspot\src\share\vm\interpreter\ByteCodes.hpp
\hotspot\src\share\vm\interpreter\ByteCodes.cpp
关于new这个字节码,对应的TemplateTable的static void _new()函数
以X86_64机器为例,代码位于\hotspot\src\cpu\x86\vm\templateTable_x86_64.cpp中
代码为汇编代码,比较冗长,只挑关键的列出:
movptr(rsi, Address(rsi, rdx,Address::times_8, sizeof(constantPoolOopDesc))); 获取到instanceKlass
movl(rdx, Address(rsi, Klass::layout_helper_offset_in_bytes() + sizeof(oopDesc))); 从instanceKlass中获取到instance对象的大小
// Allocate the instance
// 1) Try to allocate in the TLAB
// 2) if fail and the object is large allocate in the shared Eden
// 3) if the above fails (or is not applicable), go to a slow case
if (UseTLAB) {
movptr(rax, Address(r15_thread, in_bytes(JavaThread::tlab_top_offset()))); 获取到tlab的开始位移
lea(rbx, Address(rax, rdx, Address::times_1)); 在rbx寄存器中放入XX地址
cmpptr(rbx, Address(r15_thread, in_bytes(JavaThread::tlab_end_offset()))); 获取到tlab的结束位移
jcc(Assembler::above, allow_shared_alloc ? allocate_shared : slow_case); 如果…..跳转到在共享新生代分配
movptr(Address(r15_thread, in_bytes(JavaThread::tlab_top_offset())), rbx); 移动tlab的顶部指针 ->即缓存区释放出的这部分内存,即是申请的Tlab内存
……跳转到header初始化或者Object初始化
}
if (allow_shared_alloc) {
bind(allocate_shared); 打LABEL 共享新生代分配
ExternalAddress top((address)Universe::heap()->top_addr()); 获取到当前堆的顶部
ExternalAddress end((address)Universe::heap()->end_addr()); 获取到当前堆的底部
lea(RtopAddr, top);
lea(RendAddr, end);
movptr(rax, Address(RtopAddr, 0));
Label retry;
bind(retry); 开始循环
lea(rbx, Address(rax, rdx, Address::times_1));
cmpptr(rbx, Address(RendAddr, 0));
jcc(Assembler::above, slow_case);
if (os::is_MP()) { 增加内存屏障,保证读可见
lock();
}
cmpxchgptr(rbx, Address(RtopAddr, 0)); 原子性更新heap->top_addr并增加内存
jcc(Assembler::notEqual, retry); 如果不成果,则重新跳转到retry中执行
incr_allocated_bytes(r15_thread, rdx, 0);
}
if (UseTLAB || Universe::heap()->supports_inline_contig_alloc()) {
bind(initialize_object);
decrementl(rdx, sizeof(oopDesc));
jcc(Assembler::zero, initialize_header);
// Initialize object fields
xorl(rcx, rcx); // use zero reg to clear memory (shorter code)
shrl(rdx, LogBytesPerLong); // divide by oopSize to simplify the loop
{
Label loop;
bind(loop);
movq(Address(rax, rdx, Address::times_8,sizeof(oopDesc) - oopSize), rcx);
decrementl(rdx);
jcc(Assembler::notZero, loop);
}
// initialize object header only.
bind(initialize_header);
if (UseBiasedLocking) {
movptr(rscratch1, Address(rsi, Klass::prototype_header_offset_in_bytes() + klassOopDesc::klass_part_offset_in_bytes()));
movptr(Address(rax, oopDesc::mark_offset_in_bytes()), rscratch1);
} else {
movptr(Address(rax, oopDesc::mark_offset_in_bytes()), (intptr_t) markOopDesc::prototype()); // header (address 0x1)
}
xorl(rcx, rcx); // use zero reg to clear memory (shorter code)
store_klass_gap(rax, rcx); // zero klass gap for compressed oops
store_klass(rax, rsi); // store klass last
jmp(done);
}
// slow case 慢分配方式
bind(slow_case);
get_constant_pool(c_rarg1);
get_unsigned_2_byte_index_at_bcp(c_rarg2, 1);
call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), c_rarg1, c_rarg2); 调用运行时解释器_new,位于\hotspot\src\share\vm\interpreter\interpreterRuntime.cpp
oop obj = klass->allocate_instance(CHECK); 在堆中分配内存
位于\hotspot\src\share\vm\oops\instanceKlass.cpp
代码: i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL); 即调用当前的堆进行分配内存
代码: HeapWord* obj = common_mem_allocate_init(size, false, CHECK_NULL);
代码: post_allocation_setup_obj(klass, obj, size);
verify_oop(rax);
// continue 申请完毕
__ bind(done);
从以上步骤,即可看到:new字节码,在JVM中的运行机制。(需要熟悉汇编,汇编语言会根据机型,操作系统,编译器,有不同的写法。建议大家读懂一种,然后再开始,例如,我学的是X86_64CPU,LINUX操作系统,GCC编译器)
下文,我们将从JVM的角度,看一下new关键字,在触发了slow case慢分配之后,在JVM堆中是如何运行的。
1、入口:
入口为\hotspot\src\cpu\x86\vm\templateTable_x86_64.cpp中的_new函数的slow case的label。
代码: call_VM(rax, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), c_rarg1, c_rarg2);
调用: \hotspot\src\share\vm\interpreter\interpreterRuntime.cpp
InterpreterRuntime::_new(JavaThread thread, constantPoolOopDesc pool, int index)
调用: oop obj = klass->allocate_instance(CHECK);
调用: instanceOop instanceKlass::allocate_instance(TRAPS) 位于:
调用: instanceOop i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
调用: oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS)
调用: HeapWord obj = common_mem_allocate_init(size, false, CHECK_NULL);
调用: HeapWord obj = common_mem_allocate_noinit(size, is_noref, CHECK_NULL);
调用: HeapWord result = CollectedHeap::allocate_from_tlab(THREAD, size);
HeapWord result = Universe::heap()->mem_allocate(size,is_noref,false,&gc_overhead_limit_was_exceeded);
调用: HeapWord GenCollectedHeap::mem_allocate(size_t size,bool is_large_noref,bool is_tlab,bool gc_overhead_limit_was_exceeded)
调用: collector_policy()->mem_allocate_work(size,is_tlab,gc_overhead_limit_was_exceeded);
调用: HeapWord GenCollectorPolicy::mem_allocate_work(size_t size,bool is_tlab,bool gc_overhead_limit_was_exceeded)
代码: HeapWord* result = NULL;
result = gen0->par_allocate(size, is_tlab);//尝试从新生代或者tlab上分配,如果分配成果则返回
MutexLocker ml(Heap_lock); 尝试加锁后从堆中分配
result = gch->attempt_allocation(size, is_tlab, first_only);//依次尝试从堆的各内存代中分配内存
result = expand_heap_and_allocate(size, is_tlab); //尝试扩展内存代并分配内存
VM_GenCollectForAllocation op(size, is_tlab, gc_count_before);
VMThread::execute(&op); 触发一次GC
continue; 如果还是失败,则重试分配流程
调用: init_obj(obj, size);
代码: const size_t hs = oopDesc::header_size();
代码: ((oop)obj)->set_klass_gap(0);
代码: Copy::fill_to_aligned_words(obj + hs, size - hs); 对对象进行初始化,因此field int 的初始值为0,但是在方法中int i 不初始化会报错。
调用: post_allocation_setup_obj(klass, obj, size);
代码: post_allocation_setup_common(klass, obj, size);
代码: post_allocation_setup_no_klass_install(klass, obj, size);
代码: if (UseBiasedLocking && (klass() != NULL)) { obj->set_mark(klass->prototype_header());} 如果使用偏向锁,则设置obj的mark
代码: else obj->set_mark(markOopDesc::prototype());
代码: post_allocation_install_obj_klass(klass, oop(obj), (int) size);
代码: obj->set_klass(klass());
代码: post_allocation_notify(klass, (oop)obj);
代码: LowMemoryDetector::detect_low_memory_for_collected_pools(); 低内存通知
代码: JvmtiExport::vm_object_alloc_event_collector(obj); JVMTI工具
至此之后,一个new指令,调用了tlab指令,调用了堆的分配内存指令,调用了堆的setup_obj指令,将对象分配成功,初始化成果,完成了对象创建的流程。
至于java对象创建后的invokespecial调用构造函数,下期再讲。