创建对象时,需要在堆上申请指定大小的内存,如果同时有大量线程申请内存的话,可以通过锁机制或者指针碰撞的方式确保不会申请到同一块内存,在JVM运行中,内存分配是一个极其频繁的动作,这种方式势必会降低性能。TLAB是虚拟机在堆内存的eden划分出来的一块专用空间,是线程专属的。在虚拟机的TLAB功能启动的情况下,在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用,这样每个线程都单独拥有一个空间,如果需要分配内存,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。这里值得注意的是,我们说TLAB是线程独享的,但是只是在“分配”这个动作上是线程独享的,至于在读取、垃圾回收等动作上都是线程共享的。而且在使用上也没有什么区别。
也就是说,虽然每个线程在初始化时都会去堆内存中申请一块TLAB,并不是说这个TLAB区域的内存其他线程就完全无法访问了,其他线程的读取还是可以的,只不过无法在这个区域中分配内存而已。
并且,在TLAB分配之后,并不影响对象的移动和回收,也就是说,虽然对象刚开始可能通过TLAB分配内存,存放在Eden区,但是还是会被垃圾回收或者被移到Survivor Space、Old Gen等。
还有一点需要注意的是,我们说TLAB是在eden区分配的,因为eden区域本身就不太大,而且TLAB空间的内存也非常小,默认情况下仅占有整个Eden空间的1%。所以,必然存在一些大对象是无法在TLAB直接分配。
遇到TLAB中无法分配的大对象,对象还是可能在eden区或者老年代等进行分配的,但是这种分配就需要进行同步控制,这也是为什么我们经常说:小的对象比大的对象分配起来更加高效。
TLAB带来的问题
虽然在一定程度上,TLAB大大的提升了对象的分配速度,但是TLAB并不是就没有任何问题的。
前面我们说过,因为TLAB内存区域并不是很大,所以,有可能会经常出现不够的情况。在《实战Java虚拟机》中有这样一个例子:
比如一个线程的TLAB空间有100KB,其中已经使用了80KB,当需要再分配一个30KB的对象时,就无法直接在TLAB中分配,遇到这种情况时,有两种处理方案:
1、如果一个对象需要的空间大小超过TLAB中剩余的空间大小,则直接在堆内存中对该对象进行内存分配。
2、如果一个对象需要的空间大小超过TLAB中剩余的空间大小,则废弃当前TLAB,重新申请TLAB空间再次进行内存分配。
以上两个方案各有利弊,如果采用方案1,那么就可能存在着一种极端情况,就是TLAB只剩下1KB,就会导致后续需要分配的大多数对象都需要在堆内存直接分配。
如果采用方案2,也有可能存在频繁废弃TLAB,频繁申请TLAB的情况,而我们知道,虽然在TLAB上分配内存是线程独享的,但是TLAB内存自己从堆中划分出来的过程确实可能存在冲突的,所以,TLAB的分配过程其实也是需要并发控制的。而频繁的TLAB分配就失去了使用TLAB的意义。
为了解决这两个方案存在的问题,虚拟机定义了一个refill_waste的值,这个值可以翻译为“最大浪费空间”。
当请求分配的内存大于refill_waste的时候,会选择在堆内存中分配。若小于refill_waste值,则会废弃当前TLAB,重新创建TLAB进行对象内存分配。
前面的例子中,TLAB总空间100KB,使用了80KB,剩余20KB,如果设置的refill_waste的值为25KB,那么如果新对象的内存大于25KB,则直接堆内存分配,如果小于25KB,则会废弃掉之前的那个TLAB,重新分配一个TLAB空间,给新对象分配内存。
TLAB使用的相关参数
TLAB功能是可以选择开启或者关闭的,可以通过设置-XX:+/-UseTLAB参数来指定是否开启TLAB分配。
TLAB默认是eden区的1%,可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。
默认情况下,TLAB的空间会在运行时不断调整,使系统达到最佳的运行状态。如果需要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB来禁用,并且使用-XX:TLABSize来手工指定TLAB的大小。
TLAB的refill_waste也是可以调整的,默认值为64,即表示使用约为1/64空间大小作为refill_waste,使用参数:-XX:TLABRefillWasteFraction来调整。
如果想要观察TLAB的使用情况,可以使用参数-XX+PringTLAB 进行跟踪。
总结
为了保证对象的内存分配过程中的线程安全性,HotSpot虚拟机提供了一种叫做TLAB(Thread Local Allocation Buffer)的技术。
在线程初始化时,虚拟机会为每个线程分配一块TLAB空间,只给当前线程使用,当需要分配内存时,就在自己的空间上分配,这样就不存在竞争的情况,可以大大提升分配效率。
所以,“堆是线程共享的内存区域”这句话并不完全正确,因为TLAB是堆内存的一部分,它在读取上确实是线程共享的,但是在内存分配上,是线程独享的。
TLAB的空间其实并不大,所以大对象还是可能需要在堆内存中直接分配。那么,对象的内存分配步骤就是先尝试TLAB分配,空间不足之后,再判断是否应该直接进入老年代,然后再确定是再eden分配还是在老年代分配。
下面我们继续探讨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对象的创建过程,
源码说明如下:
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()方法得出,如下图:
![image.png](https://upload-images.jianshu.io/upload_images/26273155-9cde033034f0ec5b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
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);)
}