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 收集器工作流程相关推荐

  1. JStorm与Storm源码分析(六)--收集器 IOutputCollector 、OutputCollector

    在Storm中,多个地方使用了IOutputCollector收集器接口,收集器OutputCollector的接口就是IOutputCollector.所以有必要对接口IOutputCollecto ...

  2. CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇

    在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分,下面一些概念目前尚未有公开的文档和书籍讲到. 为了分析这部分我花了一个多月的时间,期间也多次向Cor ...

  3. ABP源码分析四十七:ABP中的异常处理

    ABP源码分析四十七:ABP中的异常处理 参考文章: (1)ABP源码分析四十七:ABP中的异常处理 (2)https://www.cnblogs.com/1zhk/p/5538983.html (3 ...

  4. 【投屏】Scrcpy源码分析四(最终章 - Server篇)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  5. gSOAP 源码分析(四)

    gSOAP 源码分析(四) 2012-6-2 邵盛松 前言 本文主要说明gSOAP中对Client的认证分析 gSOAP中包含了HTTP基本认证,NTLM认证等,还可以自定义SOAP Heard实现认 ...

  6. Spring 源码分析(四) ——MVC(二)概述

    随时随地技术实战干货,获取项目源码.学习资料,请关注源代码社区公众号(ydmsq666) from:Spring 源码分析(四) --MVC(二)概述 - 水门-kay的个人页面 - OSCHINA ...

  7. React Native 源码分析(三)——Native View创建流程

    1.React Native 源码分析(一)-- 启动流程 2.React Native 源码分析(二)-- 通信机制 3.React Native 源码分析(三)-- Native View创建流程 ...

  8. Spring AOP 源码分析 - 筛选合适的通知器

    1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...

  9. springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三)

    springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三) 例: package com.example.demo.service;import com.exa ...

  10. Mybatis 源码分析(一)配置文件加载流程

    Mybatis 源码分析(一)配置文件加载流程 1.项目构建 引入依赖 <dependency><groupId>org.mybatis</groupId>< ...

最新文章

  1. solrCloud源码分析之CloudSolrClient
  2. PHP线程安全和非线程安全的区别
  3. ACID中C与CAP定理中C的区别
  4. matlab二元方程组,用matlab解一个二元方程组,会的进,得到解再回答
  5. 这款能够生成文档的接口测试软件,为什么越来越受欢迎?
  6. mysql主流版本2020_mysql高级2020.7.12-2020.7.13
  7. rosdep init 和rosdep update的解决方法,亲测有效
  8. Mysql 中的SSL 连接
  9. codevs 1766 装果子
  10. STM32CubeMX学习笔记(26)——SDIO接口使用(读写SD卡)
  11. centos系统下安装daemontools详细指南
  12. linux消息队列默认长度,Linux 消息队列长度处理
  13. 马悦凌:从初级护士到“民间奇医”[2]
  14. 计算机网络带宽确定,一种计算机网络可用带宽测量方法
  15. Fields、Fieldvalues和Fieldbyname的区别
  16. 达人秀计算机教学,回顾〡信息技术达人秀
  17. 在Home Assistant 添加MariaDB数据库
  18. Python实现socket简单一对一聊天
  19. java如何解除文件锁定状态_如何使用Python解锁锁定的文件和文件夹(mac)
  20. SQL 多表联合查询,收藏直接起飞!

热门文章

  1. JS async库:parallel, series, waterfall, whilst用法
  2. 【有奖征询】可查询商票及企业境外债软件有奖征询
  3. Flex ANE制作打包流程
  4. 计算机英文积累(一)
  5. mysql横切竖切_MySQL常用操作 - cheney-f的个人空间 - OSCHINA - 中文开源技术交流社区...
  6. KUCAS清关文件申请形式 TER与TIR认证介绍
  7. 高通---IGV:从安装到使用
  8. Linux C/C++ 共享库so的搜索路径和顺序
  9. 最新Discuz【西瓜】微信登录插件
  10. Y7000联想拯救者gtx1050Ti安装cuda9.0