概述
在JDK1.9中移除了JDK 8中已弃用的垃圾收集器(GC)组合,官网原始文档如下:
Removes garbage collector (GC) combinations that were deprecated in JDK 8.
This means that the following GC combinations no longer exist:
- DefNew + CMS
- ParNew + SerialOld
- Incremental CMS
The "foreground" mode for Concurrent Mark Sweep (CMS) has also been
removed. The following command-line flags have been removed:
- -Xincgc
- -XX:+CMSIncrementalMode
- -XX:+UseCMSCompactAtFullCollection
- -XX:+CMSFullGCsBeforeCompaction
- -XX:+UseCMSCollectionPassing
The command line flag -XX:+UseParNewGC no longer has an effect. ParNew can only be used with CMS and CMS requires ParNew. Thus, the -XX:+UseParNewGC flag has been deprecated and will likely be removed in a future release.
在JDK1.9以后,将不再推荐使用CMS垃圾回收器,官方推荐使用 G1 垃圾回收器,官方原始文档如下:
Makes Garbage-First (G1) the default garbage collector (GC) on 32- and 64-bit >server configurations. Using a low-pause collector such as G1 provides a better >overall experience, for most users, than a throughput-oriented collector such as >the Parallel GC, which was previously the default.
由此可见,在未来G1将成为JVM虚拟机中主流的垃圾回收器,接下来我们去了解一下在使用 G1 的情况下,对象的分配流程。
对象分配通有流程
对象的分配流程如下图:
- 判断对象是否已经加载,如果已经加载,执行步骤 3,否则执行步骤 2
- 通过类加载器加载类的字节码文件,对字节码文件进行验证、准备、解析等操作,进入步骤 3
- 为对象分配内存空间,首先尝试在TLAB空间分配对象,如果分配失败,在eden空间分配
- 在eden空间分配对象,如果分配成功则对对象进行初始化,并将对象引用赋给变量,如果分配失败则触发GC,进行垃圾回收,对内存空间进行回收以后,重新尝试对象的分配。
以上是对象分配的通有流程,但是在采用不同垃圾回收器的情况下,对象的分配流程有所差异,下面我们来研究一下在使用G1的情况下,对象分配的流程是怎么样的。
G1对象分配
程序启动时,使用模板解释器来执行字节码,当执行new操作时,调用下面代码完成对象创建工作,源码地址:openjdk\hotspot\src\share\vm\interpreter\interpreterRuntime.cpp
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, constantPoolOopDesc* pool, int index))
//从运行时常量池中获取KlassOop
klassOop k_oop = pool->klass_at(index, CHECK);
instanceKlassHandle klass (THREAD, k_oop);
// 确保我们没有实例化一个抽象的klass
klass->check_valid_for_instantiation(true, CHECK);
// 保证已经完成类加载和初始化
klass->initialize(CHECK);
//分配对象
oop obj = klass->allocate_instance(CHECK);
thread->set_vm_result(obj);
IRT_END
在创建对象之前,从运行时常量池中获取 Klass 对象,调用check_valid_for_instantiation 方法,确保我们初始化的类不是一个抽象类,接下来检查对象是否已经加载(如果未加载,通过类加载器加载class),最后调用allocate_instance 方法,创建对象,接下来我们深入进去,查看源码具体实现。源码地址:openjdk\hotspot\src\share\vm\oops\instanceKlass.cpp
instanceOop instanceKlass::allocate_instance(TRAPS) {
assert(!oop_is_instanceMirror(), "wrong allocation path");
//是否重写finalize()方法
bool has_finalizer_flag = has_finalizer(); // Query before possible GC
//分配的对象的大小
int size = size_helper(); // Query before forming handle.
KlassHandle h_k(THREAD, as_klassOop());
instanceOop i;
//分配对象
i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
通过调用 instanceKlass::allocate_instance 方法,分配对象:首先判断类是否重写finalize()方法,获取对象需要分配的内存空间的大小,然后创建对象,如果之前判断对象重写了finalize()方法,将该对象注册到 Finalizer 队列里面。在该方法中调用CollectedHeap::obj_allocate方法创建对象,继续往下深入,源码地址:openjdk\hotspot\src\share\vm\gc_interface\collectedHeap.inline.hpp
//对象内存空间分配
oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {
debug_only(check_for_valid_allocation_state());
//校验在GC的时候不分配内存
assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
//分配大小大于0
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);
NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
return (oop)obj;
}
在上面的代码中,通过断言判断现在是否在执行GC,以及对象分配的大小是否大于0,如果断言通过,进行内存空间的分配,然后对对象进行初始化操作。关于对象的内存分配主要时调用CollectedHeap类的 common_mem_allocate_init 方法,代码如下:
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;
}
从上面的代码中可以看出,主要是调用 common_mem_allocate_noinit 方法分配了内存空间,然后调用 init_obj 方法对分配的内存进行填充。继续深入到 common_mem_allocate_noinit方法中,查看内存分配的详情。
HeapWord* CollectedHeap::common_mem_allocate_noinit(KlassHandle klass, size_t size, TRAPS) {
.............................
HeapWord* result = NULL;
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;
//在堆中分配
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;
}
..............................
//抛出异常
THROW_OOP_0(Universe::out_of_memory_error_gc_overhead_limit());
}
}
上面贴了部分关键代码,首先对象会尝试在TLAB空间分配对象,如果分配失败,则尝试在Eden空间分配内存,如果在Eden空间也分配失败,就会抛出常见的OutOfMemoryError的异常,下面分别讲解如何在TLAB空间和Eden空间划分内存空间。
G1内存结构
在讲对象如何在TLAB空间和Eden空间分配之前,我们先了解一下使用G1垃圾回收器时,内存结构是什么样的。G1内存结构图如下:
在使用G1垃圾收集器的情况下,堆被划分为多个内存大小相等的Region,每个Region被划分到不同的年龄代中,下面我们结合源码,去查看一下在使用G1的情况下,Region的空间大小。源码地址:hotspot\src\share\vm\gc_implementation\g1\heapRegion.cpp
//最小region大小
#define MIN_REGION_SIZE ( 1024 * 1024 )
//最大region大小
#define MAX_REGION_SIZE ( 32 * 1024 * 1024 )
//region个数
#define TARGET_REGION_NUMBER 2048
void HeapRegion::setup_heap_region_size(uintx min_heap_size) {
// region_size in bytes
uintx region_size = G1HeapRegionSize;
if (FLAG_IS_DEFAULT(G1HeapRegionSize)) {
region_size = MAX2(min_heap_size / TARGET_REGION_NUMBER,
(uintx) MIN_REGION_SIZE);
}
int region_size_log = log2_long((jlong) region_size);
//region_size是2的指数
region_size = ((uintx)1 << region_size_log);
// Now make sure that we don't go over or under our limits.
if (region_size < MIN_REGION_SIZE) {
region_size = MIN_REGION_SIZE;
} else if (region_size > MAX_REGION_SIZE) {
region_size = MAX_REGION_SIZE;
}
// And recalculate the log.
region_size_log = log2_long((jlong) region_size);
....................................省略..........................................
}
从上面的代码可以看出,G1默认region的最小大小为1M,最大大小为32M,且每个region的大小是2^n次,同时默认将堆划分为2048块。
TLAB空间对象分配
上面小节,我们分析了G1的内存结构,这小节我们分析对象如何在TLAB空间分配。通过调用 allocate_from_tlab 方法,实现在TLAB空间分配对象,代码如下:
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;
}
// Otherwise..
//慢分配
return allocate_from_tlab_slow(klass, thread, size);
}
在上面的代码中,先尝试在当前线程指向的TLAB空间分配(通过指针碰撞算法分配对象),如果对象分配成功,则返回对象,如果分配失败,则进入慢分配流程。接下来我们分析一下慢分配流程:
HeapWord* CollectedHeap::allocate_from_tlab_slow(KlassHandle klass, Thread* thread, size_t size) {
//TLAB空间剩余大小大于可忽略大小
if (thread->tlab().free() > thread->tlab().refill_waste_limit()) {
//重设可忽略大小
thread->tlab().record_slow_allocation(size);
return NULL;
}
//计算新TLAB空间大小
size_t new_tlab_size = thread->tlab().compute_size(size);
thread->tlab().clear_before_allocation();
if (new_tlab_size == 0) {
return NULL;
}
// 分配新的TLAB空间
HeapWord* obj = Universe::heap()->allocate_new_tlab(new_tlab_size);
if (obj == NULL) {
return NULL;
}
........................省略..........................
//分配对象
thread->tlab().fill(obj, obj + size, new_tlab_size);
return obj;
}
在上面的代码中,先判断TLAB空间剩余的大小是否大于可忽略的大小,如果满足,就重新设置可忽略大小,如果不满足,重新申请一片TLAB空间,进行对象分配,在使用G1垃圾回收器的情况下,申请新TLAB空间的具体实现如下,源码地址:hotspot\src\share\vm\gc_implementation\g1\g1CollectedHeap.cpp
HeapWord* G1CollectedHeap::allocate_new_tlab(size_t word_size) {
assert_heap_not_locked_and_not_at_safepoint();
assert(!isHumongous(word_size), "we do not allow humongous TLABs");
unsigned int dummy_gc_count_before;
return attempt_allocation(word_size, &dummy_gc_count_before);
}
上面的代码中主要是调用 attempt_allocation 方法分配内存空间,继续深入查看代码
G1CollectedHeap::attempt_allocation(size_t word_size,
unsigned int* gc_count_before_ret) {
assert_heap_not_locked_and_not_at_safepoint();
assert(!isHumongous(word_size), "attempt_allocation() should not "
"be called for humongous allocation requests");
//_mutator_alloc_region内部持有一个引用_alloc_region,指向当前正活跃的eden region
HeapWord* result = _mutator_alloc_region.attempt_allocation(word_size,
//通过CAS分配对象失败,通过加锁分配内存 false /* bot_updates */);
if (result == NULL) {
result = attempt_allocation_slow(word_size, gc_count_before_ret);
}
assert_heap_not_locked();
if (result != NULL) {
dirty_young_block(result, word_size);
}
return result;
}
从上面可以看出,通过 _mutator_alloc_region 对象分配TLAB空间,在 _mutator_alloc_region 对象中,有个 _alloc_region 指针,该指针指向当前活跃的 region 空间,如果分配失败,则通过加锁的方式来分配对象。接下我们查看一下attempt_allocation 方法的实现
inline HeapWord* G1AllocRegion::attempt_allocation(size_t word_size,
bool bot_updates) {
assert(bot_updates == _bot_updates, ar_ext_msg(this, "pre-condition"));
//获得当前活跃的region
HeapRegion* alloc_region = _alloc_region;
assert(alloc_region != NULL, ar_ext_msg(this, "not initialized properly"));
//分配对象内存
HeapWord* result = par_allocate(alloc_region, word_size, bot_updates);
if (result != NULL) {
trace("alloc", word_size, result);
return result;
}
trace("alloc failed", word_size);
return NULL;
}
在该方法中,我们看到了 _alloc_region 指针,该指针执行当前活跃的 region,然后调用 par_allocate 方法,分配TLAB空间, par_allocate 最终回调用 par_allocate_impl 方法,分配TLAB空间。源码地址:hotspot\src\share\vm\memory\space.cpp
inline HeapWord* ContiguousSpace::par_allocate_impl(size_t size,
HeapWord* const end_value) {
do {
HeapWord* obj = top();
if (pointer_delta(end_value, obj) >= size) {
HeapWord* new_top = obj + size;
//采用指针碰撞方式,分配对象内存(CAS)
HeapWord* result = (HeapWord*)Atomic::cmpxchg_ptr(new_top, top_addr(), obj);
// result can be one of two:
// the old top value: the exchange succeeded
// otherwise: the new value of the top is returned.
if (result == obj) {
assert(is_aligned(obj) && is_aligned(new_top), "checking alignment");
return obj;
}
} else {
return NULL;
}
} while (true);
}
如果通过 _mutator_alloc_region 对象分配TLAB空间失败,则会调用 attempt_allocation_slow 方法,通过该方法分配TLAB空间,源码如下:
HeapWord* G1CollectedHeap::attempt_allocation_slow(size_t word_size,
unsigned int *gc_count_before_ret) {
HeapWord* result = NULL;
for (int try_count = 1; /* we'll return */; try_count += 1) {
bool should_try_gc;
unsigned int gc_count_before;
{
MutexLockerEx x(Heap_lock);//获取堆的锁
result = _mutator_alloc_region.attempt_allocation_locked(word_size,
false /* bot_updates */);
if (result != NULL) {
return result;
}
//如果执行到这里,说明内存分配失败
assert(_mutator_alloc_region.get() == NULL, "only way to get here");
if (GC_locker::is_active_and_needs_gc()) {//JNI方法调用,执行扩容操作】
//如果还有空闲的区域
if (g1_policy()->can_expand_young_list()) {
// No need for an ergo verbose message here,
// can_expand_young_list() does this when it returns true.
result = _mutator_alloc_region.attempt_allocation_force(word_size,
false /* bot_updates */);
if (result != NULL) {
return result;
}
}
should_try_gc = false;
} else {
if (GC_locker::needs_gc()) {
should_try_gc = false;
} else {
// Read the GC count while still holding the Heap_lock.
gc_count_before = total_collections();
should_try_gc = true;
}
}
}
if (should_try_gc) {
bool succeeded;
//触发GC
result = do_collection_pause(word_size, gc_count_before, &succeeded);
if (result != NULL) {
assert(succeeded, "only way to get back a non-NULL result");
return result;
}
...............................中间省略部分代码..................................
result = _mutator_alloc_region.attempt_allocation(word_size,
false /* bot_updates */);
if (result != NULL) {
return result;
}
}
该方法篇幅较长,主要是先获得堆空间的锁,然后调用 attempt_allocation_locked 方法分配内存,如果分配失败,判断当前是否在执行JNI方法的调用,如果是执行扩容操作,如果不是则判断是否需要GC,如果 should_try_gc 为true,调用do_collection_pause 方法,触发一次GC操作。接下来,我们分析一下 attempt_allocation_locked 方法,源码如下:
inline HeapWord* G1AllocRegion::attempt_allocation_locked(size_t word_size,
bool bot_updates) {
// First we have to tedo the allocation, assuming we're holding the
// appropriate lock, in case another thread changed the region while
// we were waiting to get the lock.
HeapWord* result = attempt_allocation(word_size, bot_updates);
if (result != NULL) {
return result;
}
retire(true /* fill_up */);
result = new_alloc_region_and_allocate(word_size, false /* force */);
if (result != NULL) {
trace("alloc locked (second attempt)", word_size, result);
return result;
}
trace("alloc locked failed", word_size);
return NULL;
}
从上面可以看出,会先通过调用 attempt_allocation 方法分配内存,由于上面已经分析过该方法,就不再次分析,如果调用 attempt_allocation 方法分配失败,则调用 new_alloc_region_and_allocate 方法,分配内存,具体实现如下,源码地址:\hotspot\src\share\vm\gc_implementation\g1\g1AllocRegion.cpp
HeapWord* G1AllocRegion::new_alloc_region_and_allocate(size_t word_size,
bool force) {
assert(_alloc_region == _dummy_region, ar_ext_msg(this, "pre-condition"));
assert(_used_bytes_before == 0, ar_ext_msg(this, "pre-condition"));
trace("attempting region allocation");
//分配一个新的region
HeapRegion* new_alloc_region = allocate_new_region(word_size, force);
if (new_alloc_region != NULL) {
new_alloc_region->reset_pre_dummy_top();
// Need to do this before the allocation
_used_bytes_before = new_alloc_region->used();
HeapWord* result = allocate(new_alloc_region, word_size, _bot_updates);
assert(result != NULL, ar_ext_msg(this, "the allocation should succeeded"));
OrderAccess::storestore();
_alloc_region = new_alloc_region;
_count += 1;
trace("region allocation successful");
return result;
} else {
trace("region allocation failed");
return NULL;
}
ShouldNotReachHere();
}
该方法中调用了 allocate_new_region 方法分配新的 region,该方法最终调用 new_mutator_alloc_region 方法,分配region,具体实现如下:
HeapRegion* G1CollectedHeap::new_mutator_alloc_region(size_t word_size,
bool force) {
assert_heap_locked_or_at_safepoint(true /* should_be_vm_thread */);
assert(!force || g1_policy()->can_expand_young_list(),
"if force is true we should be able to expand the young list");
//判断当前young_list中的region数是否已经超过阈值
bool young_list_full = g1_policy()->is_young_list_full();
if (force || !young_list_full) {
HeapRegion* new_alloc_region = new_region(word_size,
false /* do_expand */);
if (new_alloc_region != NULL) {
set_region_short_lived_locked(new_alloc_region);
_hr_printer.alloc(new_alloc_region, G1HRPrinter::Eden, young_list_full);
return new_alloc_region;
}
}
return NULL;
}
在 new_mutator_alloc_region 判断是否强强制分配内存,或者当前young_list中的region数是否已经超过阈值,如果判断成功,新分配一个region,如果失败,然后NULL;到此对象在TLAB空间如果分配的流程分析结束。
Eden空间分配
上一小节分析了在TLAB空间分配内存,如果在TLAB空间分配失败,则在Eden空间分配,在Eden空间分配主要是调用了下面方法:
//在堆中分配
result = Universe::heap()->mem_allocate(size,
&gc_overhead_limit_was_exceeded);
在使用的G1垃圾收集器的情况下,内存被划分为多个region,将不同region划分给不同的年代,接下来分析在使用G1的情况下,对象如何分配,深入到 mem_allocate 方法中,源码地址:hotspot\src\share\vm\gc_implementation\g1\g1CollectedHeap.cpp
HeapWord*
G1CollectedHeap::mem_allocate(size_t word_size,
bool* gc_overhead_limit_was_exceeded) {
assert_heap_not_locked_and_not_at_safepoint();
for (int try_count = 1; /* we'll return */; try_count += 1) {
unsigned int gc_count_before;
HeapWord* result = NULL;
if (!isHumongous(word_size)) {
//在Region里面分配对象
result = attempt_allocation(word_size, &gc_count_before);
} else {
//在region中分配大对象
result = attempt_allocation_humongous(word_size, &gc_count_before);
}
if (result != NULL) {
return result;
}
// 触发GC
VM_G1CollectForAllocation op(gc_count_before, word_size);
// ...and get the VM thread to execute it.
VMThread::execute(&op);
..........................省略.............................
}
在 mem_allocate 方法中,判断分配的对象是否是巨型对象,如果不是巨型对象,调用 attempt_allocation,该方法在上一小节已经分析,如果是巨型对象,调用attempt_allocation_humongous 方法,分配内存空间,如果分配失败,这触发一次GC,因为之前已经分析了 attempt_allocation 方法,该小节就不分析了,我们主要分析 attempt_allocation_humongous 方法,关键代码如下,源码地址:hotspot\src\share\vm\gc_implementation\g1\g1AllocRegion.cpp
{
MutexLockerEx x(Heap_lock);
//巨型对象不在年代代分配,先尝试分配
result = humongous_obj_allocate(word_size);
if (result != NULL) {
return result;
}
if (GC_locker::is_active_and_needs_gc()) {
should_try_gc = false;
} else {
//判断是否进行GC
if (GC_locker::needs_gc()) {
should_try_gc = false;
} else {
gc_count_before = total_collections();
should_try_gc = true;
}
}
}
if (should_try_gc) {
//触发GC
bool succeeded;
result = do_collection_pause(word_size, gc_count_before, &succeeded);
if (result != NULL) {
assert(succeeded, "only way to get back a non-NULL result");
return result;
}
....................省略..........................
}
在上面的方法中先尝试分配巨型对象,如果对象分配不成功,则判断是否触发GC,接下来我们分析 humongous_obj_allocate 方法的实现,关键代码如下:
size_t word_size_rounded = round_to(word_size, HeapRegion::GrainWords);
//需要的region数
uint num_regions = (uint) (word_size_rounded / HeapRegion::GrainWords);
uint x_num = expansion_regions();
uint fs = _hrs.free_suffix();
//从空闲可用的region列表中找到多个连续的region,并返回第一个region的序号
uint first = humongous_obj_allocate_find_first(num_regions, word_size);
if (first == G1_NULL_HRS_INDEX) {
// The only thing we can do now is attempt expansion.
if (fs + x_num >= num_regions) {
assert(num_regions > fs, "earlier allocation should have succeeded");
ergo_verbose1(ErgoHeapSizing,
"attempt heap expansion",
ergo_format_reason("humongous allocation request failed")
ergo_format_byte("allocation request"),
word_size * HeapWordSize);
if (expand((num_regions - fs) * HeapRegion::GrainBytes)) {
first = humongous_obj_allocate_find_first(num_regions, word_size);
}
}
}
HeapWord* result = NULL;
if (first != G1_NULL_HRS_INDEX) {
//分配内存
result =
humongous_obj_allocate_initialize_regions(first, num_regions, word_size);
assert(result != NULL, "it should always return a valid result");
//
g1mm()->update_sizes();
}
在该方法中,判断分配该巨型对象需要多少个region,并从空间的region列表中分配多个连续的region,返回第一个region的序号,接下来判断是否用那么多空闲的region,如果没有,进行扩容操作,接下来进行对象的分配。
参考链接
//www.greatytc.com/p/a0efa489b99f
自我介绍
我是何勇,现在重庆猪八戒,多学学!!!