Serial垃圾回收器Minor GC

1. DefNewGeneration垃圾回收

新生代使用复制算法做垃圾回收,比老年代的标记-压缩简单很多,所有回收代码都位于DefNewGeneration::collect:

// hotspot\share\gc\serial\defNewGeneration.cpp
void DefNewGeneration::collect(bool full,
bool clear_all_soft_refs,
size_t size,
bool is_tlab) {
SerialHeap* heap = SerialHeap::heap();
_old_gen = heap->old_gen();
// 如果新生代全是存活对象,老年代可能容不下新生代的晋升
// 则设置增量垃圾回收失败,直接返回
if (!collection_attempt_is_safe()) {
heap->set_incremental_collection_failed();
return;
}
...
// 各种闭包初始化
IsAliveClosure is_alive(this);
... {
// 扫描GC Root,用快速扫描闭包做对象复制
StrongRootsScope srs(0);
heap->young_process_roots(&srs,
&fsc_with_no_gc_barrier,
&fsc_with_gc_barrier,
&cld_scan_closure);
}
// 用快速成员处理闭包处理非GC Root直达对象
evacuate_followers.do_void();
// 特殊处理软引用,弱引用,虚引用,final引用
... // 如果晋升成功,则清空eden,from;交换from,to分区;调整老年代晋升阈值
// 同时还需要确保晋升成功的情况下to区一定是空的
if (!_promotion_failed) {
eden()->clear(SpaceDecorator::Mangle);
from()->clear(SpaceDecorator::Mangle);
if (ZapUnusedHeapArea) {
to()->mangle_unused_area();
}
swap_spaces();
adjust_desired_tenuring_threshold();
AdaptiveSizePolicy* size_policy = heap->size_policy();
size_policy->reset_gc_overhead_limit_count();
}
// 否则晋升失败,提醒老年代
else {
_promo_failure_scan_stack.clear(true);
remove_forwarding_pointers();
log_info(gc, promotion)("Promotion failed");
swap_spaces();
from()->set_next_compaction_space(to());
heap->set_incremental_collection_failed();
_old_gen->promotion_failure_occurred();
}
// 更新gc日志,清除preserved mar
...
}

在做Minor GC之前会检查此次垃圾回收是否安全(collection_attempt_is_safe),所谓是否安全是指最坏情况下新生代全是需要晋升的存活对象,这时候老年代能否安全容纳下。如果JVM回答可以做垃圾回收,那么再做下面的展开。

2. 快速扫描闭包(FastScanClosure)

新生代的复制动作主要位于young_process_roots(),该函数首先会扫描所有类型的GC Root,使用快速扫描闭包配合GC Root将直达的存活对象复制到To survivor区,然后再扫描从老年代指向新生代的应用。快速扫描闭包指的是FastScanClosure,它的代码如下:

// hotspot\share\gc\shared\genOopClosures.inline.hpp
inline void FastScanClosure::do_oop(oop* p) { FastScanClosure::do_oop_work(p); }
template <class T> inline void FastScanClosure::do_oop_work(T* p) {
// 从地址p处获取对象
T heap_oop = RawAccess<>::oop_load(p);
if (!CompressedOops::is_null(heap_oop)) {
oop obj = CompressedOops::decode_not_null(heap_oop);
// 如果对象位于新生代
if ((HeapWord*)obj < _boundary) {
// 如果对象有转发指针(相当于已复制过)就保持原位
// 否则根据情况进行复制
oop new_obj = obj->is_forwarded() ? obj->forwardee()
: _g->copy_to_survivor_space(obj);
RawAccess<IS_NOT_NULL>::oop_store(p, new_obj);
if (is_scanning_a_cld()) {
do_cld_barrier();
} else if (_gc_barrier) {
// 根据情况设置gc barrier
do_barrier(p);
}
}
}
}

一句话总结,快速扫描闭包的能力是视情况复制地址所指对象或者晋升它。这段代码有两个值得提及的地方:

  1. 根据情况进行复制的copy_to_survivor_space()
  2. 根据情况设置gc屏障的do_barrier()

2.1 新生代到To survivor的复制

先说第一个复制:

// hotspot\share\gc\serial\defNewGeneration.cpp
oop DefNewGeneration::copy_to_survivor_space(oop old) {
size_t s = old->size();
oop obj = NULL;
// 如果对象还年轻就在to区分配空间
if (old->age() < tenuring_threshold()) {
obj = (oop) to()->allocate_aligned(s);
}
// 如果对象比较老或者to区分配失败,晋升到老年代
if (obj == NULL) {
obj = _old_gen->promote(old, s);
if (obj == NULL) { // 晋升失败处理
handle_promotion_failure(old);
return old;
}
} else {
// 如果to分配成功,在新分配的空间里面放入对象
const intx interval = PrefetchCopyIntervalInBytes;
Prefetch::write(obj, interval);
Copy::aligned_disjoint_words((HeapWord*)old, (HeapWord*)obj, s);
// 对象年龄递增且加入年龄表
obj->incr_age();
age_table()->add(obj, s);
}
// 把新地址插入对象mark word,表示该对象已经复制过了。
old->forward_to(obj);
return obj;
}

代码很清晰,如果GC Root里面引用的对象年龄没有超过晋升阈值,就把它从新生代(Eden+From)转移到To,如果超过阈值直接从新生代转移到老年代。

2.2 GC屏障

然后说说gc barrier。之前文章提到过老年代(TenuredGeneration,久任代)继承自卡表代(CardGeneration),卡表代把堆空间划分为一张张512字节的卡片,如果某个卡是脏卡(dirty card)就表示该卡表示的512字节内存空间存在指向新生代的对象,就需要扫描这篇区域。do_barrier()会检查是否开启gc barrier,是否老年代地址p指向的对象存在指向新生代的对象。如果条件都满足就会将卡标记为dirty,那么具体是怎么做的?

//hotspot\share\gc\shared\cardTableRS.hpp
class CardTableRS: public CardTable {
...
void inline_write_ref_field_gc(void* field, oop new_val) {
jbyte* byte = byte_for(field);
*byte = youngergen_card;
}
}

field表示这个老年代对象的地址,byte_for()会找到该地址对应的card,然后*byte = youngergen_card标记为脏卡,再来看看byte_for()又是怎么根据地址找到card的:

//hotspot\share\gc\shared\cardTable.hpp
class CardTable: public CHeapObj<mtGC> {
...
jbyte* byte_for(const void* p) const {
jbyte* result = &_byte_map_base[uintptr_t(p) >> card_shift];
return result;
}
}

card_shift表示常量9,卡表是一个字节数组,每个字节映射老年代512字节,计算方法就是当前地址除以512向下取整,然后查找卡表数组对应的字节:

3. 快速成员处理闭包(FastEvacuateFollowersClosure)

不难看出,快速扫描闭包只是复制和晋升了GC Root直接可达的对象引用。但问题是对象还可能有成员,可达性分析是从GC Root出发寻找对象引用,以及对象成员的引用,对象成员的成员的引用...快速成员处理闭包正是处理剩下不那么直接的对象引用:

//hotspot\share\gc\serial\defNewGeneration.cpp
void DefNewGeneration::FastEvacuateFollowersClosure::do_void() {
do {
// 对整个堆引用快速成员处理闭包,注意快速扫描闭包是不能单独行动的
// 他还需要借助快速扫描闭包的力量,因为快速扫描闭包有复制对象的能力
// _scan_cur_or_nonheap表示快速扫描闭包
// _scan_older表示带gc屏障的快速扫描闭包
_heap->oop_since_save_marks_iterate(_scan_cur_or_nonheap, _scan_older);
} while (!_heap->no_allocs_since_save_marks());
}

第一步快速扫描闭包可能会将Eden+From区的对象提升到老年代或者复制到To区,也就相当于此时的"GC Root"变成了To+老年代,所以快速成员处理闭包需要处理这两个代,但是不知道为什么Hotspot还额外处理了From+Eden...这个留待探究,先看看处理方法:

//hotspot\share\gc\shared\space.inline.hpp
template <typename OopClosureType>
void ContiguousSpace::oop_since_save_marks_iterate(OopClosureType* blk) {
HeapWord* t;
// 扫描指针为灰色对象开始
HeapWord* p = saved_mark_word();
const intx interval = PrefetchScanIntervalInBytes;
do {
// 灰色对象结束
t = top();
while (p < t) {
Prefetch::write(p, interval);
oop m = oop(p);
// 迭代处理对象m的成员&&返回对象m的大小
// 扫描指针向前推进
p += m->oop_iterate_size(blk);
}
} while (t < top());
set_saved_mark_word(p);
}

比较坑的是oop_iterate_size()函数会同时迭代处理对象m的成员并返回对象m的大小...还要注意oop_iterate_size()传入的blk表示的是快速扫描闭包,同样一句话总结,快速成员处理闭包的能力是递归式处理一个分区所有对象及对象成员,这种能力配合上快速扫描闭包最终效果就是将一个分区的对象视情况复制到到To survivor区或者晋升到老年代。

关于快速扫描闭包和快速成员处理闭包用图片说明可能更简单,假设有ABCD四个对象:

当快速扫描闭包完成时A假设会进入To区域:

当快速成员处理闭包完成时A的成员B和老年代C指向的成员D也会进入To:

[Inside HotSpot] Serial垃圾回收器 (二) Minor GC的更多相关文章

  1. [Inside HotSpot] Serial垃圾回收器 (一) Full GC

    Serial垃圾回收器Full GC Serial垃圾回收器的Full GC使用标记-压缩(Mark-Compact)进行垃圾回收,该算法基于Donald E. Knuth提出的Lisp2算法,它会把 ...

  2. Hotspot JVM垃圾回收器

    前两篇<JVM入门——运行时数据区><JVM常见垃圾回收算法>所提到的实际上JVM规范以及常用的垃圾回收算法,具体的JVM实现实际上不止一种,有JRockit.J9等待,当然最 ...

  3. HotSpot的垃圾回收器

    如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现.这里讨论的收集器基于JDK 1.7 Update 14之后的 HotSpot 虚拟机,这个虚拟机包含的所有收集器如下图所示 上图 ...

  4. 深入理解JVM虚拟机3:垃圾回收器详解

    JVM GC基本原理与GC算法 Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Java深受大家欢迎的众多特性之一,能够帮助程 ...

  5. JVM垃圾回收器、内存分配与回收策略

    新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...

  6. JVM性能调优(2) —— 垃圾回收器和回收策略

    一.垃圾回收机制 1.为什么需要垃圾回收 Java 程序在虚拟机中运行,是会占用内存资源的,比如创建的对象.加载的类型数据等,而且内存资源都是有限的.当创建的对象不再被引用时,就需要被回收掉,释放内存 ...

  7. JVM几种垃圾回收器介绍

    整理自:http://www.cnblogs.com/lspz/p/6397649.html 一.如何回收? 1.1 垃圾收集算法: (1)标记-清除(Mark-Sweep)算法 这是最基础的算法,就 ...

  8. 【转】Java学习---垃圾回收算法与 JVM 垃圾回收器综述

    [原文]https://www.toutiao.com/i6593931841462338062/ 垃圾回收算法与 JVM 垃圾回收器综述 我们常说的垃圾回收算法可以分为两部分:对象的查找算法与真正的 ...

  9. 垃圾回收算法与 JVM 垃圾回收器综述(转)

    垃圾回收算法与 JVM 垃圾回收器综述 我们常说的垃圾回收算法可以分为两部分:对象的查找算法与真正的回收方法.不同回收器的实现细节各有不同,但总的来说基本所有的回收器都会关注如下两个方面:找出所有的存 ...

随机推荐

  1. WebStorm换主题(护眼)

    一.下载喜欢颜色的主题 http://www.phpstorm-themes.com/ 我用的豆沙绿护眼 <scheme name="Solarized Light My" ...

  2. [LUOGU] P2330 [SCOI2005]繁忙的都市

    题目描述 城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造.城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条 ...

  3. django-ckeditor添加代码功能(codesnippet)

    最近做了一个博客,使用python3+django2.1开发的,后台编辑器和前端显示用的Django-ckeditor富文本编辑器,由于发现没有代码块功能,写上去的代码在前端展示有点乱,于是一顿问度娘 ...

  4. easyUI之datagrid绑定后端返回数据的两种方式

    先来看一下某一位大佬留下的easyUI的API对datagrid绑定数据的两种方式的介绍. 虽然精简,但是,很具有“师傅领进门,修行靠个人”的精神,先发自内心的赞一个. 但是,很多人和小编一样,第一次 ...

  5. perl学习一:探索Perl世界

    1.perl4种变量 scalar: $ 纯变量,标量array: @hash: %file: 大写 2.变量组成 1.命名规则 大小写敏感 . 字母数字下划线...2.无关键字,$+其他,但不可以与 ...

  6. read content in a text file in python

    ** read text file in pythoncapability: reading =text= from a text file 1. open the IDLE text editor  ...

  7. 总线(bus);设备(devices);驱动(drivers)

    Linux Cross Reference Free Electrons Embedded Linux Experts • Source Navigation  • Diff Markup  • Id ...

  8. PAT Basic 1017

    1017 A除以B(20 分) 本题要求计算 A/B,其中 A 是不超过 1000 位的正整数,B 是 1 位正整数.你需要输出商数 Q 和余数 R,使得 A=B×Q+R 成立. 输入格式: 输入在一 ...

  9. Spring核心技术(十一)——基于Java的容器配置(一)

    基本概念: @Bean和@Configuration Spring中新的基于Java的配置的核心就是支持@Configuration注解的类以及@Bean注解的方法. @Bean注解用来表示一个方法会 ...

  10. 项目-开发手机app

    一.  安装Hbuilder,和夜神安卓模拟器 注:夜神模拟器,如过windows中安装了hyper-v,需要卸载,不然会死机 二. Hbuilder简介 官网:http://www.dcloud.i ...