1.对象创建 new 关键字
代码依然在 Klass/Oops 模块中,首先创建对象时通过 javap -v 查看字节码文件会发现 new 指令,这个函数由解释器提供,也就是说C++解释器和模板解释器都有对应的 new 指令实现。其中C++解释器代码比较好读 bytecodeInterpreter.cpp:
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);
}
2.堆上TLAB机制
里面涉及到 tlab 机制,主要是为了解决在Eden区分配内存时锁竞争导致性能下降的问题。
ThreadLocalAllocBuffer 的定义位于 hotspot/src/share/vm/memory/ThreadLocalAllocBuffer.hpp中。
具体的实现在同目录的ThreadLocalAllocBuffer.inline.cpp和ThreadLocalAllocBuffer.cpp中,该类表示由线程私有的内存区域,这里的私有具体是指只能由该线程在这块内存区域中分配对象,但是已经分配的对象其他线程也可以正常访问。
当开发者使用JAVA语言实例化一个对象时,排除JIT的标量替换等优化手段,该对象会在JAVA Heap上分配存储空间。G1支持基于TLAB的快速分配,当TLAB快速分配失败时,使用TLAB外慢速分配。
堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的。JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。也就是说,Java中每个线程都会有自己的缓冲区称作TLAB(Thread-local allocation buffer),每个TLAB都只有一个线程可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的锁进行同步,也就是说,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可。需要通过下面的参数进行开启:
-XX:UseTLAB
当然也可以在栈上面直接分配对象:
如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上,**这样,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以减小垃圾收集器的负载。JVM允许将线程私有的对象打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能。栈上分配的技术基础:
一是逃逸分析:逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。
二是标量替换:允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
只能在server模式下才能启用逃逸分析,参数-XX:DoEscapeAnalysis启用逃逸分析,参数-XX:+EliminateAllocations开启标量替换(默认打开)。Java SE 6u23版本之后,HotSpot中默认就开启了逃逸分析,可以通过选项-XX:+PrintEscapeAnalysis查看逃逸分析的筛选结果。
ThreadLocalAllocBuffer 类定义如下:
class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {
friend class VMStructs;
private:
HeapWord* _start; // HeapWord指针,TLAB内存区域的起始地址
HeapWord* _top; // HeapWord指针,最后一次分配后的地址,即该地址之前的内存区域都已经被分配了
HeapWord* _pf_top; // HeapWord指针,预分配的top
HeapWord* _end; // HeapWord指针,TLAB内存区域的结束地址,不包含保留区域
size_t _desired_size; // 期望的内存大小,包含保留区域,以字宽为单位,即8字节
size_t _refill_waste_limit; // 一个阈值,free()的返回值大于此值,则保留此TLAB,否则丢弃创建一个新的TLAB
size_t _allocated_before_last_gc; // 最近一次gc前已分配的内存大小
static size_t _max_size; // TLAB的最大大小,静态属性
static unsigned _target_refills; // 在GC前的目标refill的次数,静态属性
unsigned _number_of_refills; // 执行refill的次数,即重新分配TLAB的次数
unsigned _fast_refill_waste; // 走快速分配refill浪费的内存大小
unsigned _slow_refill_waste; // 走慢速分配refill浪费的内存大小
unsigned _gc_waste; // 因为gc导致refill浪费的内存大小
unsigned _slow_allocations; // 走慢速分配的次数,通过TLAB分配是快速分配,走堆内存分配因为必须加锁是慢速分配
AdaptiveWeightedAverage _allocation_fraction; // AdaptiveWeightedAverage类实例,用于自适应调整待分配的TLAB大小
// 省略
inline HeapWord* allocate(size_t size);
void initialize(); // TLAB的初始化方法,完成线程TLAB的关键属性的初始化,并没有实际分配内存
};
ThreadLocalAllocBuffer 会在 JavaThread 初始化时进行创建
JavaThread::run() => 执行 Java new Thread() 创建线程后运行
this->initialize_tlab(); => 初始化线程的talb
tlab().initialize(); => 调用 ThreadLocalAllocBuffer.initialize() 的初始化方法
除此之外一个线程在创建新 talb时也会触发初始化
ThreadLocalAllocBuffer.fill() =>
ThreadLocalAllocBuffer.initialize()
ThreadLocalAllocBuffer.initialize() 完成线程TLAB的关键属性的初始化,并没有实际分配内存,代码如下:
void ThreadLocalAllocBuffer::initialize() {
initialize(NULL, // start
NULL, // top
NULL); // end
// 设置当前TLAB的_desired_size,该值通过initial_desired_size()方法计算
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;
_allocation_fraction.sample(alloc_frac);
}
// 设置当前TLAB的_refill_waste_limit,该值通过initial_refill_waste_limit()方法计算;
set_refill_waste_limit(initial_refill_waste_limit());
// 初始化一些统计字段,如_number_of_refills、_fast_refill_waste、_slow_refill_waste、_gc_waste和_slow_allocations
initialize_statistics();
}
如果UseTLAB参数为true,在TLAB中分配对象内存,也就是调用 ThreadLocalAllocBuffer.allocate() 函数进行对象分配。
if (UseTLAB) {
result = (oop) THREAD->tlab().allocate(obj_size);
}
分配如下:
inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
invariants();
HeapWord* obj = top();
//通过指针地址计算剩余的空间是否大于待分配的内存大小
//注意pointer_delta返回的内存大小以及这里待分配的内存大小size的单位都是字宽,8字节
if (pointer_delta(end(), obj) >= size) {
// successful thread-local allocation
#ifdef ASSERT
// Skip mangling the space corresponding to the object header to
// ensure that the returned space is not considered parsable by
// any concurrent GC thread.
size_t hdr_size = oopDesc::header_size();
Copy::fill_to_words(obj + hdr_size, size - hdr_size, badHeapWordVal);
#endif // ASSERT
// This addition is safe because we know that top is
// at least size below end, so the add can't wrap.
set_top(obj + size);
invariants();
// 返回top指针 把top指针的位置往前移动待分配空间的大小。对象大小通过InstanceKlass的size_helper()方法得出
return obj;
}
return NULL;
}
这里需要返回 top 指针,如果计算对象大小超过剩余容量那么返回 NULL 由外层处理。
3.对象创建入口-通过tlab在堆上分配
最终会调用 InstanceKlass::allocate_instance() 函数创建对象。
instanceOop InstanceKlass::allocate_instance(TRAPS) {
//判断目标类是否覆写了Object类的finalize方法
bool has_finalizer_flag = has_finalizer(); // Query before possible GC
//获取目标类的对象大小
int size = size_helper(); // Query before forming handle.
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() 并调用 CollectedHeap::common_mem_allocate_noinit() 函数。代码如下:
oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {
debug_only(check_for_valid_allocation_state());
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);
post_allocation_setup_obj(klass, obj, size);
NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
return (oop)obj;
}
在 CollectedHeap::common_mem_allocate_noinit() 中会根据是否开启 tlab 来判断是通过 tlab 来在堆上进行分配还是说直接在堆上进行分配:
HeapWord* CollectedHeap::common_mem_allocate_noinit(KlassHandle klass, size_t size, TRAPS) {
// 省略
HeapWord* result = NULL;
if (UseTLAB) {
// ThreadLocalAllocBuffer.allocate() 函数进行对象分配
result = allocate_from_tlab(klass, THREAD, size);
if (result != NULL) {
assert(!HAS_PENDING_EXCEPTION,"Unexpected exception, will result in uninitialized storage");
return result;
}
}
result = Universe::heap()->mem_allocate(size,&gc_overhead_limit_was_exceeded);
// 省略
}
如果开启 UseTLAB 则调用 CollectedHeap::allocate_from_tlab(),先调用 allocate_from_tlab() 也就是调用 ThreadLocalAllocBuffer.allocate() 函数进行对象分配:
HeapWord* CollectedHeap::allocate_from_tlab(KlassHandle klass, Thread* thread, size_t size) {
assert(UseTLAB, "should use UseTLAB");
HeapWord* obj = thread->tlab().allocate(size);
if (obj != NULL) {
return obj;
}
// Otherwise...
return allocate_from_tlab_slow(klass, thread, size);
}
前面已经分析过 thread->tlab().allocate(size); 在剩余容量不足时返回 NULL,于是调用 CollectedHeap::allocate_from_tlab_slow() 进行慢速分配:
HeapWord* CollectedHeap::allocate_from_tlab_slow(KlassHandle klass, Thread* thread, size_t size) {
// 如果当前TLAB的剩余容量大于浪费阈值,就不在当前TLAB分配,直接在共享的Eden区进行分配,并且记录慢分配的内存大小;
if (thread->tlab().free() > thread->tlab().refill_waste_limit()) {
thread->tlab().record_slow_allocation(size);
return NULL;
}
size_t new_tlab_size = thread->tlab().compute_size(size);
thread->tlab().clear_before_allocation();
if (new_tlab_size == 0) {
return NULL;
}
// 通过allocate_new_tlab()方法,从eden新分配一块裸的空间出来(这一步可能会失败),如果失败说明eden没有足够空间来分配这个新TLAB,就会触发YGC。
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) {
// ..and clear it.
Copy::zero_to_words(obj, new_tlab_size);
} else {
// ...and zap just allocated object.
#ifdef ASSERT
// Skip mangling the space corresponding to the object header to
// ensure that the returned space is not considered parsable by
// any concurrent GC thread.
size_t hdr_size = oopDesc::header_size();
Copy::fill_to_words(obj + hdr_size, new_tlab_size - hdr_size, badHeapWordVal);
#endif // ASSERT
}
// 好新的TLAB内存之后,执行TLAB的fill()方法
thread->tlab().fill(obj, obj + size, new_tlab_size);
return obj;
}
这一步会看情况在新生代中创建一个新的 tlab,然后调用他的 ThreadLocalAllocBuffer::fill() 方法进行初始化。
4.对象创建入口-直接堆分配
前面 CollectedHeap::common_mem_allocate_noinit() 函数中如果禁用 tlab 则会走 Universe::heap()->mem_allocate() 分支,这一步就是执行对分配策略。
使用 -XX:-UseTLAB禁用TLAB分配,GenCollectedHeap::mem_allocate() 最终会调用 GenCollectorPolicy::mem_allocate_work() 函数,里面定义了直接上堆上分配的逻辑,会分为三步;
首先尝试年轻代并行无锁分配。分配失败,在尝试有锁状态下分配。还失败,再扩展堆空间再试。最终没有分配到则触发GC。