Parralel Scavenge 收集器工作流程
jvm初始化的时候,有个重要的步骤是全局堆的初始化,根据vm参数的不同,又会选择不同的堆实现(堆的实现在share/vm/memory中,策略选择位于share/vm/gc_implementation, 详情见Universe::initialize_heap()
)。server模式下启动的jvm,默认采用的全局堆实现是ParallelScavengeHeap
,本文记录了jdk1.8版本的Parralel Scavenge实现,很多细节还不清楚。
一、PS收集器概览及初始化
ParallelScavengeHeap由两个区域组成,_old_gen和_young_gen,其中_young_gen又由eden,from,to三个MutableSpace
组成。大致如下:
我们可以用:
- -xmn500m 来指定新时代的大小为500m
- -XX:SurvivorRatio=8 来指定eden区和(from+to)区占比8:1.
- -XX:MaxTenuringThreshold=18 来指定晋升年龄
新生代的3个区域是连续的空间,做法是开辟一个虚拟内存,根据起始大小计算分配出eden,两个survivor区域。虚拟内存不会立马占用物理内存,每次分配对象时载入物理内存。在首次将新生代数据晋升到年老代时物理内存会急剧升高,(测试:在指定xmn大小为500m的情况下,每次new 4m大小空间,约400m时触发一次 minor gc,内存突然增长到750m)。
java的new关键字实际会作用到jvm的申请内存操作(声明的对象名称会在栈内分配),调用到interpreterRuntime.cpp中的InterpreterRuntime::_new方法,为了方便触发gc,我用了new大数组的方式来调试。
创建数组的入口函数有两个,一个是用于基本类型的内存申请, 另一个是对象数组的。
IRT_ENTRY(void, InterpreterRuntime::newarray(JavaThread* thread, BasicType type, jint size))
oop obj = oopFactory::new_typeArray(type, size, CHECK);
thread->set_vm_result(obj);
IRT_END
IRT_ENTRY(void, InterpreterRuntime::anewarray(JavaThread* thread, ConstantPool* pool, int index, jint size))
// Note: no oopHandle for pool & klass needed since they are not used
// anymore after new_objArray() and no GC can happen before.
// (This may have to change if this code changes!)
Klass* klass = pool->klass_at(index, CHECK);
objArrayOop obj = oopFactory::new_objArray(klass, size, CHECK);
thread->set_vm_result(obj);
IRT_END
申请内存需要经过以下几个步骤。
1.尝试从tlab中分配内存,分配成功直接返回;不成功进入下一步。
2.从垃圾收集器中分配内存,如果没有相关指定收集器的配置,Server模式默认的是parralel Scavenge的方式分配。
3.从新生代中分配,关键代码
HeapWord* result = young_gen()->allocate(size);
,我们知道新生代实际直接存放新建对象的区域是eden区,关键代码HeapWord* result = eden_space()->cas_allocate(word_size);
4.内存的实际分配是MutableSpace类实现的,判断当前剩余空闲内存是否足够放下申请的对象,如果可以,那么成功返回。如果不行,进入步骤5。
5.如果对象的大小大于eden区的一半,那么直接分配到老年代中去,否则进入下一步。
HeapWord* ParallelScavengeHeap::mem_allocate_old_gen(size_t size) {
if (!should_alloc_in_eden(size) || GC_locker::is_active_and_needs_gc()) {
// Size is too big for eden, or gc is locked out.
return old_gen()->allocate(size);
}
// If a "death march" is in progress, allocate from the old gen a limited
// number of times before doing a GC.
if (_death_march_count > 0) {
if (_death_march_count < 64) {
++_death_march_count;
return old_gen()->allocate(size);
} else {
_death_march_count = 0;
}
}
return NULL;
}
inline bool ParallelScavengeHeap::should_alloc_in_eden(const size_t size) const
{
const size_t eden_size = young_gen()->eden_space()->capacity_in_words();
return size < eden_size / 2;
}
- 6.触发一个
VM_ParallelGCFailedAllocation
任务抛给vm线程,这里注意下VMThread的execute方法是阻塞的,需要等到vm线程的gc任务完成当前线程才会返回。默认的任务是VM_ParallelGCFailedAllocation。查看其doit
方法
void VM_ParallelGCFailedAllocation::doit() {
SvcGCMarker sgcm(SvcGCMarker::MINOR);
ParallelScavengeHeap* heap = (ParallelScavengeHeap*)Universe::heap();
assert(heap->kind() == CollectedHeap::ParallelScavengeHeap, "must be a ParallelScavengeHeap");
GCCauseSetter gccs(heap, _gc_cause);
_result = heap->failed_mem_allocate(_word_size);
if (_result == NULL && GC_locker::is_active_and_needs_gc()) {
set_gc_locked();
}
}
HeapWord* ParallelScavengeHeap::failed_mem_allocate(size_t size) {
assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint");
assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread");
assert(!Universe::heap()->is_gc_active(), "not reentrant");
assert(!Heap_lock->owned_by_self(), "this thread should not own the Heap_lock");
// We assume that allocation in eden will fail unless we collect.
// First level allocation failure, scavenge and allocate in young gen.
GCCauseSetter gccs(this, GCCause::_allocation_failure);
const bool invoked_full_gc = PSScavenge::invoke();
HeapWord* result = young_gen()->allocate(size);
// Second level allocation failure.
// Mark sweep and allocate in young generation.
if (result == NULL && !invoked_full_gc) {
do_full_collection(false);
result = young_gen()->allocate(size);
}
death_march_check(result, size);
// Third level allocation failure.
// After mark sweep and young generation allocation failure,
// allocate in old generation.
if (result == NULL) {
result = old_gen()->allocate(size);
}
// Fourth level allocation failure. We're running out of memory.
// More complete mark sweep and allocate in young generation.
if (result == NULL) {
do_full_collection(true);
result = young_gen()->allocate(size);
}
// Fifth level allocation failure.
// After more complete mark sweep, allocate in old generation.
if (result == NULL) {
result = old_gen()->allocate(size);
}
return result;
}
- 7.可以看到,先执行一次minor gc,然后尝试在新生代分配。如果不成功且minor gc执行过程中没有去full gc,那么要来一次fullgc,尝试从新生代中分配。 还不成功,再来一次fullgc(带清楚软引用的),从新生代中分配。依然不成功,从老年代分配。还不成功?OOM吧。。
重点是const bool invoked_full_gc = PSScavenge::invoke();
贴一下实现,先执行一次minor gc。不成功的话,触发fullgc。fullgc根据配置有两种选择,PSMarkSweep和PSParallel。没有CMS,因为Parraler Scavenge收集器和他无法同时使用。
bool PSScavenge::invoke() {
assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint");
assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread");
assert(!Universe::heap()->is_gc_active(), "not reentrant");
ParallelScavengeHeap* const heap = (ParallelScavengeHeap*)Universe::heap();
assert(heap->kind() == CollectedHeap::ParallelScavengeHeap, "Sanity");
PSAdaptiveSizePolicy* policy = heap->size_policy();
IsGCActiveMark mark;
const bool scavenge_done = PSScavenge::invoke_no_policy();
const bool need_full_gc = !scavenge_done ||
policy->should_full_GC(heap->old_gen()->free_in_bytes());
bool full_gc_done = false;
if (UsePerfData) {
PSGCAdaptivePolicyCounters* const counters = heap->gc_policy_counters();
const int ffs_val = need_full_gc ? full_follows_scavenge : not_skipped;
counters->update_full_follows_scavenge(ffs_val);
}
if (need_full_gc) {
GCCauseSetter gccs(heap, GCCause::_adaptive_size_policy);
CollectorPolicy* cp = heap->collector_policy();
const bool clear_all_softrefs = cp->should_clear_all_soft_refs();
if (UseParallelOldGC) {
full_gc_done = PSParallelCompact::invoke_no_policy(clear_all_softrefs);
} else {
full_gc_done = PSMarkSweep::invoke_no_policy(clear_all_softrefs);
}
}
return full_gc_done;
}
接下来进入重点了,minor gc的具体实现在PSScavenge::invoke_no_policy
,代码太长不贴了。简单说下流程。
1.sanity check,记录gc前的内存信息。
2.向gc worker线程池投递gc任务,根据根节点搜索法保存有效的对象。根节点有很多种,如下。注意这些gc任务是并发执行的。
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::universe));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jni_handles));
// We scan the thread roots in parallel
Threads::create_thread_roots_tasks(q);
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::object_synchronizer));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::flat_profiler));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::management));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::system_dictionary));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::class_loader_data));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jvmti));
q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::code_cache));
可达性分析算法实现都差不多,步骤是遍历根节点的所有可达对象,判断对象是不是在新生代(根据内存地址判断),是的话调用PSPromotionManager::copy_to_survivor_space
转移存活对象,如果指定了需要晋升或者对象的年龄(新生代对象每经过一次minor gc加1岁)达到阙值,则拷贝到old区,否则拷贝到to区。
3.清空eden区和from区,再将有存活的对象的to区和from区做交换(保证存活对象放在from区,这样下次gc的时候就可以再次执行复制算法将对象拷贝到to区了)。
-
- 调用
resize_young_gen
方法重新分配新时代大小(疑问:看注释是minor gc会引起新生代大小的变化,具体什么情况?)
- 调用
4.如果有对象晋升失败了,那么可能是old区空间不足了,此时需要触发一次full gc,如果老年代的策略是ps old,那么处理老年代的gc由类
PSParallelCompact
,这一段后续分析。