Hotspot Java对象创建和TLAB源码解析

目录

一、new关键字

二、new指令

三、InterpreterRuntime::_new

四、CollectedHeap::obj_allocate

五、JNI对象创建

1、jni_AllocObject

2、jni_NewObject

六、ThreadLocalAllocBuffer

1、定义

2、ThreadLocalAllocBuffer::initialize()

2、ThreadLocalAllocBuffer::allocate(size_t size)

3、CollectedHeap::allocate_from_tlab_slow

4、ThreadLocalAllocBuffer::clear_before_allocation()


在之前的文章《Hotspot class文件和字节码解析》《Hotspot 类加载、链接和初始化 C++源码解析》中我们详细探讨了class文件的构成,class文件是如何加载,链接和初始化的,下面我们继续探讨class文件加载完成后是如何创建对应的类对象的。

一、new关键字

    Java中是通过new关键字创建对象的,new关键字实际对应字节码指令new和一次构造方法调用,测试代码如下:

package jni;

public class NewTest {

    public NewTest(int a){

    }

    public void newTest(){
        NewTest a=new NewTest(1);
    }
}

编译过后,执行javap -v 查看newTest方法的字节码,结果如下图:

new指令的说明如下:

其他指令的详细解释参考JVM虚拟机规范,简单说明下这些指令执行后的结果,new指令执行完成操作数栈顶就是新创建的对象引用;dup指令是将栈顶的操作数复制出来然后插入到栈顶,即dup指令执行完栈顶的前面两个元素都是新创建的对象的引用;iconst_1是将常量1压入栈顶;invokespecial指令是调用构造方法,该指令会将栈顶的常量1和新创建的对象引用弹出栈,将其作为参数调用构造方法,执行完成栈顶只剩下新创建的对象引用;astore_1指令是将操作栈栈顶的新创建对象引用弹出并放到方法的局部变量表中。

二、new指令

       因为Hotspot有两个解释器,一种是基于switch-and-dispatch,一种基于汇编指令模板,因此new指令的实现有两个地方可以参考,对应的两种实现分别是hotspot/src/share/vm/interpreter/bytecodeInterpretor.cpp(从代码2179行开始)和hotspot/src/cpu/x86/vm/templateTable_64_x86.cpp中,第二个涉及诸多的汇编模板方法,这里不做讨论,第一个直接使用C++写的,更加清晰直观,以此为入口了解Java对象的创建过程,这两种解释器的实现的差异参考:《HotSpot中执行引擎技术详解(二)——线索解释与模板解释器》

 源码说明如下:

CASE(_new): {
        //获取操作数栈中目标类的符号引用在常量池的索引
        u2 index = Bytes::get_Java_u2(pc+1);
        //获取当前执行的方法的类的常量池,istate是当前字节码解释器BytecodeInterpreter实例的指针
        ConstantPool* constants = istate->method()->constants();
        //如果目标类已经解析
        if (!constants->tag_at(index).is_unresolved_klass()) {
          //校验从常量池获取的解析结果Klass指针是否是InstanceKlass指针,
          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表示是否需要初始化
            bool need_zero = !ZeroTLAB;
            //如果UseTLAB参数为true,在TLAB中分配对象内存
            if (UseTLAB) {
              result = (oop) THREAD->tlab().allocate(obj_size);
            }
          
            //如果使用Profile则当TLAB分配失败必须使用InterpreterRuntime::_new()方法分配内存,此时不能走
            //ifndef圈起来的一段逻辑
#ifndef CC_INTERP_PROFILE
            if (result == NULL) {
              need_zero = true;
              //尝试在共享的eden区分配
            retry:
              //获取当前未分配内存空间的起始地址
              HeapWord* compare_to = *Universe::heap()->top_addr();
              //起始地址加上目标类对象大小后,判断是否超过eden区的终止地址
              HeapWord* new_top = compare_to + obj_size;
              if (new_top <= *Universe::heap()->end_addr()) {
                //如果没有超过则通过原子CAS的方式尝试分配,分配失败就一直尝试直到不能分配为止
                //cmpxchg_ptr函数是比较top_addr的地址和compare_to的地址是否一样,如果一样则将new_top的地址写入top_addr中并返回compare_to
                //如果不相等,即此时eden区分配了新对象,则返回top_addr新的地址,即返回结果不等于compare_to
                if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
                  goto retry;
                }
                result = (oop) compare_to;
              }
            }
#endif
            //如果分配成功
            if (result != NULL) {
              //如果需要完成对象初始化
              if (need_zero ) {
                HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;
                obj_size -= sizeof(oopDesc) / oopSize;
                if (obj_size > 0 ) {
                  //将目标对象的内存置0
                  memset(to_zero, 0, obj_size * HeapWordSize);
                }
              }
              //设置对象头
              if (UseBiasedLocking) {
               //如果使用偏向锁
                result->set_mark(ik->prototype_header());
              } else {
                result->set_mark(markOopDesc::prototype());
              }
              //设置oop的相关属性
              result->set_klass_gap(0);
              result->set_klass(k_entry);
              //执行内存屏障指令
              OrderAccess::storestore();
              //将目标对象放到操作数栈的顶部
              SET_STACK_OBJECT(result, 0);
              //更新PC指令计数器,即告诉解释器此条new指令执行完毕,new指令总共3个字节,计数器加3
              UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
            }
          }
        }
        //调用InterpreterRuntime::_new执行慢速分配
        CALL_VM(InterpreterRuntime::_new(THREAD, METHOD->constants(), index),
                handle_exception);
        OrderAccess::storestore();
        //分配的对象保存在vm_result中,将对象放到操作数栈的顶部
        SET_STACK_OBJECT(THREAD->vm_result(), 0);
        //vm_result置空
        THREAD->set_vm_result(NULL);
        //更新PC指令计数器
        UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
      }

 如果满足以下条件则不能使用快速分配的方式创建:

  • 目标类是抽象类
  • 目标类覆写了Object的finalizer方法
  • 目标类大于FastAllocateSizeLimit参数的值,该参数默认是128k
  • 目标类是java/lang/Class,不能直接分配

参考 hotspot/src/share/vm/oops/instanceKlass.hpp中can_be_fastpath_allocated()方法的注释,如下图:

 FastAllocateSizeLimit参数的定义位于hotspot/src/share/vm/runtime/globals.hpp中,如下图:

三、InterpreterRuntime::_new

      InterpreterRuntime::_new就是慢速分配的实现,在hotspot/src/share/vm/interpreter/interpretorRuntime.cpp中,具体源码说明如下:

IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
  //获取常量池中索引为index的Klass指针
  Klass* k_oop = pool->klass_at(index, CHECK);
  instanceKlassHandle klass (THREAD, k_oop);

  //校验Klass是否是抽象类,接口或者java.lang.Class,如果是则抛出异常
  klass->check_valid_for_instantiation(true, CHECK);

  //检查Klass是否已经完成初始化,如果未完成则执行初始化
  klass->initialize(CHECK);

  //创建对象
  oop obj = klass->allocate_instance(CHECK);
  //将结果放到当前线程的_vm_result属性中,该属性专门用来传递解释器执行方法调用的结果
  thread->set_vm_result(obj);
IRT_END

 对象分配的核心逻辑都在InstanceKlass::allocate_instance方法中,该方法的实现在hotspot/src/share/vm/oops/instanceKlass.cpp中,具体源码如下:

instanceOop InstanceKlass::allocate_instance(TRAPS) {
 //判断目标类是否覆写了Object类的finalize方法
  bool has_finalizer_flag = has_finalizer(); 
  //获取目标类的对象大小
  int size = size_helper();  
  KlassHandle h_k(THREAD, this);
  
  instanceOop i;
  //创建对象
  i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
  //如果覆写了finalize方法并且RegisterFinalizersAtInit为false,即不在JVM启动时完成finalize方法的注册
  if (has_finalizer_flag && !RegisterFinalizersAtInit) {
    //注册finalize方法
    i = register_finalizer(i, CHECK_NULL);
  }
  return i;
}

四、CollectedHeap::obj_allocate

       InstanceKlass::allocate_instance方法中对象创建的核心逻辑在CollectedHeap::obj_allocate中,该类定义在hotspot/src/share/vm/gc_interface/collectedHeap.hpp中,实现在同目录的collectedHeap.cpp中,具体源码说明如下:

oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {
  debug_only(check_for_valid_allocation_state());
  //检查Java堆是否正在gc
  assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
  assert(size >= 0, "int won't convert to size_t");
  //分配对象内存并完成初始化
  HeapWord* obj = common_mem_allocate_init(klass, size, CHECK_NULL);
  //设置对象头和klass属性
  post_allocation_setup_obj(klass, obj, size);
  //检查分配的内存是否正常,生产环境不执行
  NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
  return (oop)obj;
}

HeapWord* CollectedHeap::common_mem_allocate_init(KlassHandle klass, size_t size, TRAPS) {
  HeapWord* obj = common_mem_allocate_noinit(klass, size, CHECK_NULL);
  init_obj(obj, size);
  return obj;
}

HeapWord* CollectedHeap::common_mem_allocate_noinit(KlassHandle klass, size_t size, TRAPS) {

  //清理当前线程TLAB中未使用的opp
  CHECK_UNHANDLED_OOPS_ONLY(THREAD->clear_unhandled_oops();)

  //判断是否发生异常
  if (HAS_PENDING_EXCEPTION) {
    NOT_PRODUCT(guarantee(false, "Should not allocate with exception pending"));
    return NULL;  // caller does a CHECK_0 too
  }

  HeapWord* result = NULL;
  //如果使用TLAB
  if (UseTLAB) {
    //从TLAB分配对象
    result = allocate_from_tlab(klass, THREAD, size);
    if (result != NULL) {
      assert(!HAS_PENDING_EXCEPTION,
             "Unexpected exception, will result in uninitialized storage");
      return result;
    }
  }
  bool gc_overhead_limit_was_exceeded = false;
  //从Java堆中分配对象内存
  result = Universe::heap()->mem_allocate(size,
                                          &gc_overhead_limit_was_exceeded);

  //分配成功                                        
  if (result != NULL) {
    NOT_PRODUCT(Universe::heap()->
      check_for_non_bad_heap_word_value(result, size));
    assert(!HAS_PENDING_EXCEPTION,
           "Unexpected exception, will result in uninitialized storage");
    //增加当前线程记录已分配的内存大小的属性
    THREAD->incr_allocated_bytes(size * HeapWordSize);
    //发布堆内存对象分配事件
    AllocTracer::send_allocation_outside_tlab_event(klass, size * HeapWordSize);

    return result;
  }

 //分配失败
  if (!gc_overhead_limit_was_exceeded) {
    //异常处理,Java heap space表示当前堆内存严重不足
    report_java_out_of_memory("Java heap space");
    //通知JVMTI
    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP,
        "Java heap space");
    }
    //抛出异常
    THROW_OOP_0(Universe::out_of_memory_error_java_heap());
  } else {
    //同上,异常处理,GC overhead limit exceeded表示执行GC后仍不能有效回收内存导致内存不足
    report_java_out_of_memory("GC overhead limit exceeded");

    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP,
        "GC overhead limit exceeded");
    }

    THROW_OOP_0(Universe::out_of_memory_error_gc_overhead_limit());
  }
}

HeapWord* CollectedHeap::allocate_from_tlab(KlassHandle klass, Thread* thread, size_t size) {
  assert(UseTLAB, "should use UseTLAB");
  //从tlab中分配指定大小的内存
  HeapWord* obj = thread->tlab().allocate(size);
  if (obj != NULL) {
    return obj;
  }
  //走慢速模式从tlab中分配内存
  return allocate_from_tlab_slow(klass, thread, size);
}

void CollectedHeap::init_obj(HeapWord* obj, size_t size) {
  assert(obj != NULL, "cannot initialize NULL object");
  const size_t hs = oopDesc::header_size();
  assert(size >= hs, "unexpected object size");
  //设置GC分代年龄
  ((oop)obj)->set_klass_gap(0);
  //将分配的对象内存全部初始化为0
  Copy::fill_to_aligned_words(obj + hs, size - hs);
}

void report_java_out_of_memory(const char* message) {
  static jint out_of_memory_reported = 0;

  //出现OutOfMemoryError时可能多个线程并发调用此方法,但是只需调用一次即可
  if (Atomic::cmpxchg(1, &out_of_memory_reported, 0) == 0) {
    //根据不同的参数配置执行不同的动作
    if (HeapDumpOnOutOfMemoryError) {
      tty->print_cr("java.lang.OutOfMemoryError: %s", message);
      //堆内存Dump
      HeapDumper::dump_heap_from_oome();
    }

    if (OnOutOfMemoryError && OnOutOfMemoryError[0]) {
      VMError err(message);
      err.report_java_out_of_memory();
    }

    if (CrashOnOutOfMemoryError) {
      tty->print_cr("Aborting due to java.lang.OutOfMemoryError: %s", message);
      fatal(err_msg("OutOfMemory encountered: %s", message));
    }

    if (ExitOnOutOfMemoryError) {
      tty->print_cr("Terminating due to java.lang.OutOfMemoryError: %s", message);
      exit(3);
    }
  }
}


void CollectedHeap::post_allocation_setup_obj(KlassHandle klass,
                                              HeapWord* obj,
                                              int size) {
  post_allocation_setup_common(klass, obj);
  assert(Universe::is_bootstrapping() ||
         !((oop)obj)->is_array(), "must not be an array");
  //通知jvmti对象创建,如果DTraceAllocProbes为true则打印日志
  post_allocation_notify(klass, (oop)obj, size);
}


void CollectedHeap::post_allocation_setup_common(KlassHandle klass,
                                                 HeapWord* obj) {
  post_allocation_setup_no_klass_install(klass, obj);
  post_allocation_install_obj_klass(klass, oop(obj));
}

void CollectedHeap::post_allocation_setup_no_klass_install(KlassHandle klass,
                                                           HeapWord* objPtr) {
  oop obj = (oop)objPtr;

  assert(obj != NULL, "NULL object pointer");
  //设置对象头
  if (UseBiasedLocking && (klass() != NULL)) {
    obj->set_mark(klass->prototype_header());
  } else {
    // May be bootstrapping
    obj->set_mark(markOopDesc::prototype());
  }
}

void CollectedHeap::post_allocation_install_obj_klass(KlassHandle klass,
                                                   oop obj) {
  // These asserts are kind of complicated because of klassKlass
  // and the beginning of the world.
  assert(klass() != NULL || !Universe::is_fully_initialized(), "NULL klass");
  assert(klass() == NULL || klass()->is_klass(), "not a klass");
  assert(obj != NULL, "NULL object pointer");
  //设置对象的klass属性
  obj->set_klass(klass());
  assert(!Universe::is_fully_initialized() || obj->klass() != NULL,
         "missing klass");
}

 综合上述源码分析可知慢速分配时对象分配和初始化的逻辑和快速分配基本一致,最大的区别在于慢速分配会执行目标类的初始化,执行目标类的finalize方法的注册,正是因为目标类的初始化比较耗时所以才称为慢速分配。之前的文章《Hotspot 类文件加载、链接和初始化》已经探讨过类初始化的时机,其中很典型的一条是执行new,getstatic,putstatic,invokestatic指令时触发类的初始化,即某个类第一次new时执行的是慢速分配,然后该类完成初始化,下次new时基本就可以通过快速分配的方式创建了。

五、JNI对象创建

        除Java代码中通过new关键字创建对象外,也可通过JNI的AllocObject方法或者NewObject方法完成,两者的区别在于前者只是完成对象内存分配,不会调用构造方法,后者会执行构造方法,下面从源码逐一说明这两个方法的实现。

1、jni_AllocObject

JNI_ENTRY(jobject, jni_AllocObject(JNIEnv *env, jclass clazz))
  JNIWrapper("AllocObject");

  jobject ret = NULL;
  //打印Trace日志
  DT_RETURN_MARK(AllocObject, jobject, (const jobject&)ret);
  //分配对象内存
  instanceOop i = alloc_object(clazz, CHECK_NULL);
  //为其创建一个本地引用
  ret = JNIHandles::make_local(env, i);
  return ret;
JNI_END


static instanceOop alloc_object(jclass clazz, TRAPS) {
  KlassHandle k(THREAD, java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
  //检查目标class是否有效
  k()->check_valid_for_instantiation(false, CHECK_NULL);
  //确保class初始化完成
  InstanceKlass::cast(k())->initialize(CHECK_NULL);
  //分配对象内存,返回的instanceOop不是Handle
  instanceOop ih = InstanceKlass::cast(k())->allocate_instance(THREAD);
  return ih;
}

2、jni_NewObject

JNI_ENTRY(jobject, jni_NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...))
  JNIWrapper("NewObject");

  jobject obj = NULL;
  DT_RETURN_MARK(NewObject, jobject, (const jobject&)obj);
  //同AllocObject,分配对象内存
  instanceOop i = alloc_object(clazz, CHECK_NULL);
  obj = JNIHandles::make_local(env, i);
  va_list args;
  va_start(args, methodID);
  JavaValue jvalue(T_VOID);
  JNI_ArgumentPusherVaArg ap(methodID, args);
  //调用构造方法
  jni_invoke_nonstatic(env, &jvalue, obj, JNI_NONVIRTUAL, methodID, &ap, CHECK_NULL);
  va_end(args);
  return obj;
JNI_END

综上JNI创建对象实际走的是InstanceKlass::allocate_instance方法,跟InterpreterRuntime::_new方法的实现基本一致。

六、ThreadLocalAllocBuffer

1、定义

      ThreadLocalAllocBuffer的定义位于hotspot/src/share/vm/memory/ThreadLocalAllocBuffer.hpp中,具体的实现在同目录的ThreadLocalAllocBuffer.inline.cpp和ThreadLocalAllocBuffer.cpp中,该类表示由线程私有的内存区域,简称TLAB,主要是为了解决在Eden区分配内存时锁竞争导致性能下降的问题。这块区域是线程私有的,这里的私有具体是指只能由该线程在这块内存区域中分配对象,但是已经分配的对象其他线程也可以正常访问。

     ThreadLocalAllocBuffer继承自CHeapObj,提供的重要属性如下:

  • _start:HeapWord指针,TLAB内存区域的起始地址
  • _top:HeapWord指针,最后一次分配后的地址,即该地址之前的内存区域都已经被分配了
  • _pf_top:HeapWord指针,预分配的top
  • _end:HeapWord指针,TLAB内存区域的结束地址,不包含保留区域
  • _desired_size:期望的内存大小,包含保留区域,以字宽为单位,即8字节
  • _refill_waste_limit:一个阈值,free()的返回值大于此值,则保留此TLAB,否则丢弃创建一个新的TLAB
  • _allocated_before_last_gc:最近一次gc前已分配的内存大小
  • _max_size:TLAB的最大大小,静态属性
  • _target_refills:在GC前的目标refill的次数,静态属性
  • _number_of_refills:执行refill的次数,即重新分配TLAB的次数
  • _fast_refill_waste:走快速分配refill浪费的内存大小
  • _slow_refill_waste:走慢速分配refill浪费的内存大小
  • _gc_waste:因为gc导致refill浪费的内存大小
  • _slow_allocations:走慢速分配的次数,通过TLAB分配是快速分配,走堆内存分配因为必须加锁是慢速分配
  • _allocation_fraction: AdaptiveWeightedAverage类实例,用于自适应调整待分配的TLAB大小
  • _global_stats:  TLAB分配对象的统计数据

注意对HeapWord指针加减1,指针内存地址会加减HeapWord对应的字节数,即8字节。注意TLAB中所有涉及内存大小的返回值,属性,入参的单位都是字宽,就是为了跟HeapWord指针配合使用。

2、ThreadLocalAllocBuffer::initialize()

     initialize方法是TLAB的初始化方法,该方法的调用如下:

以JavaThread::run()方法为例,该方法是Java线程启动执行的方法,如下图:

 

该方法的源码如下:

void ThreadLocalAllocBuffer::initialize() {
  //属性初始化
  initialize(NULL,                    // start
             NULL,                    // top
             NULL);                   // end
  //计算TLAB期望的大小
  set_desired_size(initial_desired_size());

  if (Universe::heap() != NULL) {
    size_t capacity   = Universe::heap()->tlab_capacity(myThread()) / HeapWordSize;
    double alloc_frac = desired_size() * target_refills() / (double) capacity;
    //将当前准备分配的TLAB的大小作为样本采集
    _allocation_fraction.sample(alloc_frac);
  }

  //设置初始的refill_waste_limit
  set_refill_waste_limit(initial_refill_waste_limit());
  //初始化统计数据
  initialize_statistics();
}

void ThreadLocalAllocBuffer::initialize(HeapWord* start,
                                        HeapWord* top,
                                        HeapWord* end) {
  set_start(start);
  set_top(top);
  set_pf_top(top);
  set_end(end);
  //参数校验
  invariants();
}

void invariants() const { assert(top() >= start() && top() <= end(), "invalid tlab"); }

size_t ThreadLocalAllocBuffer::initial_desired_size() {
  size_t init_sz = 0;
   //TLABSize是JVM配置参数,默认为0,0表示由JVM自身按需决定
  if (TLABSize > 0) {
    init_sz = TLABSize / HeapWordSize;
  } else if (global_stats() != NULL) {//
    //global_stats方法返回_global_stats属性,
    //allocating_threads_avg返回的是一个由AdaptiveWeightedAverage计算的根据其他线程的TLAB的分配大小自动调整的值
    unsigned nof_threads = global_stats()->allocating_threads_avg();

    init_sz  = (Universe::heap()->tlab_capacity(myThread()) / HeapWordSize) /
                      (nof_threads * target_refills());
    init_sz = align_object_size(init_sz);
  }
  init_sz = MIN2(MAX2(init_sz, min_size()), max_size());
  return init_sz;
}

//TLABRefillWasteFraction表示因内存碎片导致refill允许浪费的最大TLAB空间,默认值64,即初始的最大浪费空间是desired_size的64分之一
size_t initial_refill_waste_limit()            { return desired_size() / TLABRefillWasteFraction; }

void ThreadLocalAllocBuffer::initialize_statistics() {
    _number_of_refills = 0;
    _fast_refill_waste = 0;
    _slow_refill_waste = 0;
    _gc_waste          = 0;
    _slow_allocations  = 0;
}

 注意上述方法只是完成线程TLAB的关键属性的初始化,并没有实际分配内存,分配内存是在该线程第一次分配对象或者当前TLAB内存不足时触发的。可以搜索initialize(HeapWord* start,HeapWord* top,HeapWord* end)的调用链查看,如下图:

即刚好跟上一节的CollectedHeap::obj_allocate关联起来了。

2、ThreadLocalAllocBuffer::allocate(size_t size)

     allocate方法用来在TLAB中分配指定大小的内存,注意这里的size的单位不是字节而是字宽,一个字宽等于8字节,其源码如下:

inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
  invariants();
  HeapWord* obj = top();
  //通过指针地址计算剩余的空间是否大于待分配的内存大小
  //注意pointer_delta返回的内存大小以及这里待分配的内存大小size的单位都是字宽,8字节
  if (pointer_delta(end(), obj) >= size) {
    //将obj的指针往前移动指定大小,将原top的地址返回
    set_top(obj + size);

    invariants();
    return obj;
  }
  return NULL;
}

inline size_t pointer_delta(const HeapWord* left, const HeapWord* right) {
  return pointer_delta(left, right, sizeof(HeapWord));
}

inline size_t pointer_delta(const void* left,
                            const void* right,
                            size_t element_size) {
  return (((uintptr_t) left) - ((uintptr_t) right)) / element_size;
}

  从源码分析可知,分配内存其实就是返回top指针,把top指针的位置往前移动待分配空间的大小。对象大小通过InstanceKlass的size_helper()方法得出,如下图:

3、CollectedHeap::allocate_from_tlab_slow

      当调用ThreadLocalAllocBuffer::allocate(size_t size)方法因为TLAB内存不足分配失败后,就会调用此方法尝试分配一个新的TLAB,在新TLAB中分配对象,参考上一节CollectedHeap::obj_allocate的分析。该方法是实际调用TLAB其他方法的入口,可以从此方法的实现判断TLAB其他方法的使用场景。具体源码分析如下:

HeapWord* CollectedHeap::allocate_from_tlab_slow(KlassHandle klass, Thread* thread, size_t size) {

  // Retain tlab and allocate object in shared space if
  // the amount free in the tlab is too large to discard.
  //如果剩余的空间大于允许refill浪费的空间则继续使用该TLAB,返回NULL后就将在Eden区分配内存,因为必须加锁,所以相对于
  //走TLAB是慢速分配
  if (thread->tlab().free() > thread->tlab().refill_waste_limit()) {
    thread->tlab().record_slow_allocation(size);
    return NULL;
  }

  
  //如果剩余的空闲小于refill浪费的空间则丢弃当前线程的TLAB,重新分配一个新的
  //为了避免内存碎片化,新分配的TLAB会比之前分配的更小,compute_size就是计算待分配的TLAB的大小,如果返回0说明Eden区内存不足
  size_t new_tlab_size = thread->tlab().compute_size(size);
  
  //这里的clear并不是释放当前TALB占用的内存,而是将剩余的允许浪费的空间用无意义的对象填充,让Eden区的内存是连续的
  //同时将top,end指针等置位NULL
  thread->tlab().clear_before_allocation();

  if (new_tlab_size == 0) {
    //Eden区堆内存不足了,返回NULL,可能会触发Eden区的垃圾回收
    return NULL;
  }

  // 分配一个新的TLAB,有可能分配失败
  HeapWord* obj = Universe::heap()->allocate_new_tlab(new_tlab_size);
  if (obj == NULL) {
    return NULL;
  }
  //发布事件通知
  AllocTracer::send_allocation_in_new_tlab_event(klass, new_tlab_size * HeapWordSize, size * HeapWordSize);

  if (ZeroTLAB) {
    //初始化
    Copy::zero_to_words(obj, new_tlab_size);
  }
  //新分配的TLAB的属性初始化 
  thread->tlab().fill(obj, obj + size, new_tlab_size);
  return obj;
}


void ThreadLocalAllocBuffer::record_slow_allocation(size_t obj_size) {
   
  //refill_waste_limit_increment方法返回配置属性TLABWasteIncrement的值,默认是4
  //增加refill_waste_limit,避免某个线程一直请求大于当前free空间的内存,但是因为free空间一直小于refill_waste_limit而一直走慢速分配的情形
  set_refill_waste_limit(refill_waste_limit() + refill_waste_limit_increment());
  //增加走慢速分配的次数
  _slow_allocations++;

  if (PrintTLAB && Verbose) {
    Thread* thrd = myThread();
    gclog_or_tty->print("TLAB: %s thread: " INTPTR_FORMAT " [id: %2d]"
                        " obj: " SIZE_FORMAT
                        " free: " SIZE_FORMAT
                        " waste: " SIZE_FORMAT "\n",
                        "slow", p2i(thrd), thrd->osthread()->thread_id(),
                        obj_size, free(), refill_waste_limit());
  }
}

inline size_t ThreadLocalAllocBuffer::compute_size(size_t obj_size) {
  //align_object_size方法用于将对象大小按照8字节向上取整,返回对象的字宽大小,
  //例如对象是14字节,取整后就是16字节,返回2
  const size_t aligned_obj_size = align_object_size(obj_size);

  //估算当前Eden区可以为当前线程分配的TLAB内存的最大值,不会触发任何垃圾回收
  //unsafe_max_tlab_alloc会保证为了减少内存碎片,分配的内存比其他的TLAB的小
  const size_t available_size = Universe::heap()->unsafe_max_tlab_alloc(myThread()) /
                                                  HeapWordSize;
  
  //取三个的最小值
  size_t new_tlab_size = MIN2(available_size, desired_size() + aligned_obj_size);

  //alignment_reserve方法返回TLAB中必须保留的内存大小,obj_plus_filler_size表示待分配内存的最低值
  const size_t obj_plus_filler_size = aligned_obj_size + alignment_reserve();
  if (new_tlab_size < obj_plus_filler_size) {
    //如果没有足够的内存空间则分配失败
    if (PrintTLAB && Verbose) {
      gclog_or_tty->print_cr("ThreadLocalAllocBuffer::compute_size(" SIZE_FORMAT ")"
                    " returns failure",
                    obj_size);
    }
    return 0;
  }
  //分配成功
  if (PrintTLAB && Verbose) {
    gclog_or_tty->print_cr("ThreadLocalAllocBuffer::compute_size(" SIZE_FORMAT ")"
                  " returns " SIZE_FORMAT,
                  obj_size, new_tlab_size);
  }
  return new_tlab_size;
}

static size_t alignment_reserve()              { return align_object_size(end_reserve()); }

//计算在TLAB尾部保留的用于refiil int[]和prefetch的空间大小
static size_t end_reserve() {
    //返回int数组的oop的对象头的大小,即int[0]的大小
    int reserve_size = typeArrayOopDesc::header_size(T_INT);
    //reserve_for_allocation_prefetch方法返回为prefetch指令在TLAB尾部预留的空间大小
    return MAX2(reserve_size, VM_Version::reserve_for_allocation_prefetch());
  }

void ThreadLocalAllocBuffer::fill(HeapWord* start,
                                  HeapWord* top,
                                  size_t    new_size) {
  //增加重新分配TLAB的次数                                
  _number_of_refills++;
  if (PrintTLAB && Verbose) {
    print_stats("fill");
  }
  assert(top <= start + new_size - alignment_reserve(), "size too small");
  //start,stop等属性初始化
  initialize(start, top, start + new_size - alignment_reserve());

  //重置refill_waste_limit
  set_refill_waste_limit(initial_refill_waste_limit());
}

    从上述源码分析可知,TLAB剩余空间不能用来分配指定大小的对象且小于允许浪费的空间时会重新分配一个新的TLAB,为了减少内存碎片,新分配的TLAB会比原来的更小,分配空间大小是会根据实际请求分配的对象大小动态调整的。分配新的TLAB后,原来老TLAB的剩余空间会用int数组或者Object类来填充,从而Eden区看起来是连续的。

4、ThreadLocalAllocBuffer::clear_before_allocation()

      放弃当前的TLAB后就会调用此方法完成旧TLAB的属性置空和剩余空间填充,具体源码分析如下:

void ThreadLocalAllocBuffer::clear_before_allocation() {
   //增加慢分配下refill浪费的内存空间
  _slow_refill_waste += (unsigned)remaining();
  //让当前TLAB变成parsable,同时丢弃它,变成parsable即在剩余的内存空间中填充int数组,从而让Eden去看起来是连续的
  make_parsable(true);   // also retire the TLAB
}

// Fills the current tlab with a dummy filler array to create
// an illusion of a contiguous Eden and optionally retires the tlab.
// Waste accounting should be done in caller as appropriate; see,
// for example, clear_before_allocation().
void ThreadLocalAllocBuffer::make_parsable(bool retire) {
  if (end() != NULL) {
    invariants();

    if (retire) {
      //增加当前线程已分配的内存数,used_bytes方法返回TLAB中使用掉的内存,不包括浪费的内存
      myThread()->incr_allocated_bytes(used_bytes());
    }
    
    //end指针不包含保留的内存空间,hard_end返回end+保留空间的地址,即将TLAB 中top后面剩余的空间都填充
    CollectedHeap::fill_with_object(top(), hard_end(), retire);

    //将其他属性置空,即该TLAB不能再用来分配对象
    if (retire || ZeroTLAB) {  // "Reset" the TLAB
      set_start(NULL);
      set_top(NULL);
      set_pf_top(NULL);
      set_end(NULL);
    }
  }
  assert(!(retire || ZeroTLAB)  ||
         (start() == NULL && end() == NULL && top() == NULL),
         "TLAB must be reset");
}

HeapWord* hard_end() const                     { return _end + alignment_reserve(); }

 static void fill_with_object(HeapWord* start, HeapWord* end, bool zap = true) {
    fill_with_object(start, pointer_delta(end, start), zap);
  }

void CollectedHeap::fill_with_object(HeapWord* start, size_t words, bool zap)
{
  DEBUG_ONLY(fill_args_check(start, words);)
  HandleMark hm;  // Free handles before leaving.
  fill_with_object_impl(start, words, zap);
}

void
CollectedHeap::fill_with_object_impl(HeapWord* start, size_t words, bool zap)
{
   //校验待填充的内存是否大于最大值
  assert(words <= filler_array_max_size(), "too big for a single object");

  if (words >= filler_array_min_size()) {
    //大于最小值用int数组填充
    fill_with_array(start, words, zap);
  } else if (words > 0) {
    assert(words == min_fill_size(), "unaligned size");
     //小于最小值用Object对象填充
    post_allocation_setup_common(SystemDictionary::Object_klass(), start);
  }
}

void
CollectedHeap::fill_with_array(HeapWord* start, size_t words, bool zap)
{
  assert(words >= filler_array_min_size(), "too small for an array");
  assert(words <= filler_array_max_size(), "too big for a single object");
  //计算待填充的内存大小
  const size_t payload_size = words - filler_array_hdr_size();
  //待填充的int数组的长度
  const size_t len = payload_size * HeapWordSize / sizeof(jint);
  assert((int)len >= 0, err_msg("size too large " SIZE_FORMAT " becomes %d", words, (int)len));

  //int数组填充
  ((arrayOop)start)->set_length((int)len);
  post_allocation_setup_common(Universe::intArrayKlassObj(), start);
  DEBUG_ONLY(zap_filler_array(start, words, zap);)
}

 参考:JVM源码分析之线程局部缓存TLAB

            RednaxelaFX、你假笨关于TLAB的一些分析总结

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值