ps scavenge java_JVM源码分析(四)Parralel Scavenge 收集器工作流程
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组成。大致如下:
image.png
我们可以用:
-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,这一段后续分析。
ps scavenge java_JVM源码分析(四)Parralel Scavenge 收集器工作流程相关推荐
- JStorm与Storm源码分析(六)--收集器 IOutputCollector 、OutputCollector
在Storm中,多个地方使用了IOutputCollector收集器接口,收集器OutputCollector的接口就是IOutputCollector.所以有必要对接口IOutputCollecto ...
- CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇
在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分,下面一些概念目前尚未有公开的文档和书籍讲到. 为了分析这部分我花了一个多月的时间,期间也多次向Cor ...
- ABP源码分析四十七:ABP中的异常处理
ABP源码分析四十七:ABP中的异常处理 参考文章: (1)ABP源码分析四十七:ABP中的异常处理 (2)https://www.cnblogs.com/1zhk/p/5538983.html (3 ...
- 【投屏】Scrcpy源码分析四(最终章 - Server篇)
Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...
- gSOAP 源码分析(四)
gSOAP 源码分析(四) 2012-6-2 邵盛松 前言 本文主要说明gSOAP中对Client的认证分析 gSOAP中包含了HTTP基本认证,NTLM认证等,还可以自定义SOAP Heard实现认 ...
- Spring 源码分析(四) ——MVC(二)概述
随时随地技术实战干货,获取项目源码.学习资料,请关注源代码社区公众号(ydmsq666) from:Spring 源码分析(四) --MVC(二)概述 - 水门-kay的个人页面 - OSCHINA ...
- React Native 源码分析(三)——Native View创建流程
1.React Native 源码分析(一)-- 启动流程 2.React Native 源码分析(二)-- 通信机制 3.React Native 源码分析(三)-- Native View创建流程 ...
- Spring AOP 源码分析 - 筛选合适的通知器
1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...
- springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三)
springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三) 例: package com.example.demo.service;import com.exa ...
- Mybatis 源码分析(一)配置文件加载流程
Mybatis 源码分析(一)配置文件加载流程 1.项目构建 引入依赖 <dependency><groupId>org.mybatis</groupId>< ...
最新文章
- solrCloud源码分析之CloudSolrClient
- PHP线程安全和非线程安全的区别
- ACID中C与CAP定理中C的区别
- matlab二元方程组,用matlab解一个二元方程组,会的进,得到解再回答
- 这款能够生成文档的接口测试软件,为什么越来越受欢迎?
- mysql主流版本2020_mysql高级2020.7.12-2020.7.13
- rosdep init 和rosdep update的解决方法,亲测有效
- Mysql 中的SSL 连接
- codevs 1766 装果子
- STM32CubeMX学习笔记(26)——SDIO接口使用(读写SD卡)
- centos系统下安装daemontools详细指南
- linux消息队列默认长度,Linux 消息队列长度处理
- 马悦凌:从初级护士到“民间奇医”[2]
- 计算机网络带宽确定,一种计算机网络可用带宽测量方法
- Fields、Fieldvalues和Fieldbyname的区别
- 达人秀计算机教学,回顾〡信息技术达人秀
- 在Home Assistant 添加MariaDB数据库
- Python实现socket简单一对一聊天
- java如何解除文件锁定状态_如何使用Python解锁锁定的文件和文件夹(mac)
- SQL 多表联合查询,收藏直接起飞!
热门文章
- JS async库:parallel, series, waterfall, whilst用法
- 【有奖征询】可查询商票及企业境外债软件有奖征询
- Flex ANE制作打包流程
- 计算机英文积累(一)
- mysql横切竖切_MySQL常用操作 - cheney-f的个人空间 - OSCHINA - 中文开源技术交流社区...
- KUCAS清关文件申请形式 TER与TIR认证介绍
- 高通---IGV:从安装到使用
- Linux C/C++ 共享库so的搜索路径和顺序
- 最新Discuz【西瓜】微信登录插件
- Y7000联想拯救者gtx1050Ti安装cuda9.0