[Inside HotSpot] Serial垃圾回收器 (二) Minor GC
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);
}
}
}
}
一句话总结,快速扫描闭包的能力是视情况复制地址所指对象或者晋升它。这段代码有两个值得提及的地方:
- 根据情况进行复制的copy_to_survivor_space()
- 根据情况设置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的更多相关文章
- [Inside HotSpot] Serial垃圾回收器 (一) Full GC
Serial垃圾回收器Full GC Serial垃圾回收器的Full GC使用标记-压缩(Mark-Compact)进行垃圾回收,该算法基于Donald E. Knuth提出的Lisp2算法,它会把 ...
- Hotspot JVM垃圾回收器
前两篇<JVM入门——运行时数据区><JVM常见垃圾回收算法>所提到的实际上JVM规范以及常用的垃圾回收算法,具体的JVM实现实际上不止一种,有JRockit.J9等待,当然最 ...
- HotSpot的垃圾回收器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现.这里讨论的收集器基于JDK 1.7 Update 14之后的 HotSpot 虚拟机,这个虚拟机包含的所有收集器如下图所示 上图 ...
- 深入理解JVM虚拟机3:垃圾回收器详解
JVM GC基本原理与GC算法 Java的内存分配与回收全部由JVM垃圾回收进程自动完成.与C语言不同,Java开发者不需要自己编写代码实现垃圾回收.这是Java深受大家欢迎的众多特性之一,能够帮助程 ...
- JVM垃圾回收器、内存分配与回收策略
新生代垃圾收集器 1. Serial收集器 serial收集器即串行收集器,是一个单线程收集器. 串行收集器在进行垃圾回收时只使用一个CPU或一条收集线程去完成垃圾回收工作,并且会暂停其他的工作线程( ...
- JVM性能调优(2) —— 垃圾回收器和回收策略
一.垃圾回收机制 1.为什么需要垃圾回收 Java 程序在虚拟机中运行,是会占用内存资源的,比如创建的对象.加载的类型数据等,而且内存资源都是有限的.当创建的对象不再被引用时,就需要被回收掉,释放内存 ...
- JVM几种垃圾回收器介绍
整理自:http://www.cnblogs.com/lspz/p/6397649.html 一.如何回收? 1.1 垃圾收集算法: (1)标记-清除(Mark-Sweep)算法 这是最基础的算法,就 ...
- 【转】Java学习---垃圾回收算法与 JVM 垃圾回收器综述
[原文]https://www.toutiao.com/i6593931841462338062/ 垃圾回收算法与 JVM 垃圾回收器综述 我们常说的垃圾回收算法可以分为两部分:对象的查找算法与真正的 ...
- 垃圾回收算法与 JVM 垃圾回收器综述(转)
垃圾回收算法与 JVM 垃圾回收器综述 我们常说的垃圾回收算法可以分为两部分:对象的查找算法与真正的回收方法.不同回收器的实现细节各有不同,但总的来说基本所有的回收器都会关注如下两个方面:找出所有的存 ...
随机推荐
- vue 动态合并单元格、并添加小计合计功能
1.效果图 2.后台返回数据格式(平铺式) 3.后台返回数据后,整理所需要展示的属性存储到(items)数组内 var obj = { "id": curItems[i].id, ...
- Promise中的next 另一个用法
const chainAsync = fns => { let curr = 0 ; const next = (...args) => fns[curr++](next,...args) ...
- Android Studio问题记录
1>Android Studio中module是什么,? 答:Android Studio是基于intellij,跟eclipse不太一样.对应关系如下: intellij的project -- ...
- cs229_part4
又到了一节很重要的课,因为这个学习理论是从统计角度为机器学习算法提供了一个理论基础. 学习理论 问题背景 先回顾一下我们第一节课提到的机器学习的组成: 第一节课只是简单的提了一下,现在我们要真正来分析 ...
- PAT Basic 1055
1055 集体照 拍集体照时队形很重要,这里对给定的 N 个人 K 排的队形设计排队规则如下: 每排人数为 N/K(向下取整),多出来的人全部站在最后一排: 后排所有人的个子都不比前排任何人矮: 每排 ...
- Java面试——从JVM角度比较equals和==的区别
1. Java中数据类型分类 1.1 基本数据类型 又称为原始数据类型,byte,short,char,int,long,float,double,boolean,他们之间的比较应该使用(== ...
- 学习javascript中的事件——事件流
事件概念: HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick.页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件.想要知道 ...
- Java-改变Class
改变一个Class对象的类型 package com.tj; public class MyClass2 { public static void main(String[] args) { Obje ...
- tinyMCE获取鼠标选中的值
今天遇到一个需求就是要在WordPress的文章编辑页面增加选中文字时要将选中值传递到弹出窗口中 解决方法: 去网上找到了下面这段代码,可以获取到选中文本 { type: 'textbox', nam ...
- 九度oj 题目1108:堆栈的使用
题目描述: 堆栈是一种基本的数据结构.堆栈具有两种基本操作方式,push 和 pop.Push一个值会将其压入栈顶,而 pop 则会将栈顶的值弹出.现在我们就来验证一下堆栈的使用. 输入: 对于每组测 ...