由于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调用构造函数,下期再讲。