jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
2017-09-09 14:46
633 查看
从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
个人源码地址:https://github.com/FlashLightNing/openjdk-notes
还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
jvm源码阅读笔记[1]:如何触发一次CMS回收
jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
jvm源码阅读笔记[3]:从内存分配到触发GC的细节
jvm源码阅读笔记[4]:从GC说到vm operation
jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
在第3篇文章中,我们总结到,当分配内存失败时,会通过VM触发一次由分配失败触发的一次GC,也就是我们经常能在GC日志里面看到的“allocation failure”
同时,在我们第4篇中也简单介绍了VMThread和VMOperation的原理和作用,写到每个VM操作的具体实现逻辑都是在它的doit()方法上。那么今天我们就来看看VM_GenCollectForAllocation的具体GC的过程和步骤。来看看vmGCOperations.cpp:
方法很短,精华内容应该就是在gch->satisfy_failed_allocation(_size, _tlab)一行了。
总结起来,大致内存分配失败触发的GC分为以下几个过程:
.判断此时是否有JNI在操作,若有,则只能通过扩堆然后分配解决,不能进行GC(原因详见第3篇文章)。
若无jni操作,则判断!gch->incremental_collection_will_fail条件。若true,则只是进行一次young gc。若false,则会触发一次fullgc(该次不会清除软引用)。
尝试进行一次内存分配。若成功则返回,若失败,则试着看看能否扩容并分配
若仍失败,最后进行一次full gc,此时会清除软引用。GC完后进行分配。
若还是失败…只能OOM了。
再来解释一下gch->incremental_collection_will_fail是干嘛的?
consult_young=true的时候,表示调用该方法时,判断此时晋升是否的安全的。若=false,表示只取上次young gc时设置的参数,此次不再进行额外的判断。
简单来讲,就是
在young gc时可能存在晋升失败的风险。老年代最大的连续可用的空间>之前的平均晋升大小,或者>年轻代使用的空间大小,则被认为是安全的。反之则是不安全。
若在执行第2步的ygc时(也就是之前的ygc没有设置晋升失败的标记),young gen在进行gc时判断了晋升情况,认为不安全,就会快速返回,从而让jvm执行第4步中的full gc。这样就达到了在晋升可能失败的情况下,由fullgc 来接替young gc的目的。当进行fullgc或者一次background式的GC后(详见第1篇文章),incremental_collection_will_fail标志就会清除。
打个比方:当系统刚开始运行后,分配了许多对象后,内存空间不够了,第一次触发一次GC。假设此时没有jni,则判断上次ygc是否设置了晋升不安全的标记。因为是此时是第一次触发,所以标记位是默认值(安全的)。那么jvm就试着开始一次ygc了。在ygc的过程中,young gen发现此时老年代放不下年轻代晋升的对象,就直接return,不进行下一步的操作了。方法return之后,走到第3步分配。分配失败然后看看能否扩堆。发现都不行,再执行第4步进行一次full gc(同时清除了晋升不安全的标记),完了之后再分配。 这样就走完了一次完整的分配失败触发GC的过程。
以下是对此部分流程的图解。图片比较长,分成2部分….
下一篇文章将写一下图中的进行young gc和进行full gc的具体流程。
从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
个人源码地址:https://github.com/FlashLightNing/openjdk-notes
还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
jvm源码阅读笔记[1]:如何触发一次CMS回收
jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
jvm源码阅读笔记[3]:从内存分配到触发GC的细节
jvm源码阅读笔记[4]:从GC说到vm operation
jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
个人源码地址:https://github.com/FlashLightNing/openjdk-notes
还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
jvm源码阅读笔记[1]:如何触发一次CMS回收
jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
jvm源码阅读笔记[3]:从内存分配到触发GC的细节
jvm源码阅读笔记[4]:从GC说到vm operation
jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
在第3篇文章中,我们总结到,当分配内存失败时,会通过VM触发一次由分配失败触发的一次GC,也就是我们经常能在GC日志里面看到的“allocation failure”
VM_GenCollectForAllocation op(size, is_tlab, gc_count_before);//VM操作 VMThread::execute(&op);
同时,在我们第4篇中也简单介绍了VMThread和VMOperation的原理和作用,写到每个VM操作的具体实现逻辑都是在它的doit()方法上。那么今天我们就来看看VM_GenCollectForAllocation的具体GC的过程和步骤。来看看vmGCOperations.cpp:
void VM_GenCollectForAllocation::doit() { SvcGCMarker sgcm(SvcGCMarker::MINOR); GenCollectedHeap* gch = GenCollectedHeap::heap(); GCCauseSetter gccs(gch, _gc_cause); //通知内存堆管理器处理一次内存分配失败 _res = gch->satisfy_failed_allocation(_size, _tlab);//res=分配的结果 assert(gch->is_in_reserved_or_null(_res), "result not in heap"); if (_res == NULL && GC_locker::is_active_and_needs_gc()) { set_gc_locked(); } }
方法很短,精华内容应该就是在gch->satisfy_failed_allocation(_size, _tlab)一行了。
HeapWord* GenCollectorPolicy::satisfy_failed_allocation(size_t size, bool is_tlab) { GenCollectedHeap *gch = GenCollectedHeap::heap(); GCCauseSetter x(gch, GCCause::_allocation_failure); HeapWord* result = NULL; assert(size != 0, "Precondition violated"); if (GC_locker::is_active_and_needs_gc()) {//表示有jni在操作内存,此时不能进行GC避免改变对象在内存的位置。详见第3篇文章。 if (!gch->is_maximal_no_gc()) {//尽量不gc result = expand_heap_and_allocate(size, is_tlab);//扩堆 } return result; } else if (!gch->incremental_collection_will_fail(false /* don't consult_young */)) { gch->do_collection(false /* full */, false /* clear_all_soft_refs */, size /* size */, is_tlab /* is_tlab */, number_of_generations() - 1 /* max_level */); } else { if (Verbose && PrintGCDetails) { gclog_or_tty->print(" :: Trying full because partial may fail :: "); } // 做一次full gc gch->do_collection(true /* full */, false /* clear_all_soft_refs */, size /* size */, is_tlab /* is_tlab */, number_of_generations() - 1 /* max_level */); } //GC完再尝试分配 result = gch->attempt_allocation(size, is_tlab, false /*first_only*/); if (result != NULL) { assert(gch->is_in_reserved(result), "result not in heap"); return result; } //如果GC完还分配失败,看看能否进行扩容和分配 result = expand_heap_and_allocate(size, is_tlab); if (result != NULL) { return result; } { UIntFlagSetting flag_change(MarkSweepAlwaysCompactCount, 1); // Make sure the heap is fully compacted /* 最后再进行一次full gc,同时清除软引用 */ gch->do_collection(true /* full */, true /* clear_all_soft_refs */, size /* size */, is_tlab /* is_tlab */, number_of_generations() - 1 /* max_level */); } /* 要是再分配不了,只能报OOM了。。。 */ result = gch->attempt_allocation(size, is_tlab, false /* first_only */); if (result != NULL) { assert(gch->is_in_reserved(result), "result not in heap"); return result; } assert(!should_clear_all_soft_refs(), "Flag should have been handled and cleared prior to this point"); return NULL; }
总结起来,大致内存分配失败触发的GC分为以下几个过程:
.判断此时是否有JNI在操作,若有,则只能通过扩堆然后分配解决,不能进行GC(原因详见第3篇文章)。
若无jni操作,则判断!gch->incremental_collection_will_fail条件。若true,则只是进行一次young gc。若false,则会触发一次fullgc(该次不会清除软引用)。
尝试进行一次内存分配。若成功则返回,若失败,则试着看看能否扩容并分配
若仍失败,最后进行一次full gc,此时会清除软引用。GC完后进行分配。
若还是失败…只能OOM了。
再来解释一下gch->incremental_collection_will_fail是干嘛的?
bool incremental_collection_will_fail(bool consult_young) { assert(heap()->collector_policy()->is_two_generation_policy(), "the following definition may not be suitable for an n(>2)-generation system"); return incremental_collection_failed() || (consult_young && !get_gen(0)->collection_attempt_is_safe()); }
consult_young=true的时候,表示调用该方法时,判断此时晋升是否的安全的。若=false,表示只取上次young gc时设置的参数,此次不再进行额外的判断。
简单来讲,就是
在young gc时可能存在晋升失败的风险。老年代最大的连续可用的空间>之前的平均晋升大小,或者>年轻代使用的空间大小,则被认为是安全的。反之则是不安全。
若在执行第2步的ygc时(也就是之前的ygc没有设置晋升失败的标记),young gen在进行gc时判断了晋升情况,认为不安全,就会快速返回,从而让jvm执行第4步中的full gc。这样就达到了在晋升可能失败的情况下,由fullgc 来接替young gc的目的。当进行fullgc或者一次background式的GC后(详见第1篇文章),incremental_collection_will_fail标志就会清除。
打个比方:当系统刚开始运行后,分配了许多对象后,内存空间不够了,第一次触发一次GC。假设此时没有jni,则判断上次ygc是否设置了晋升不安全的标记。因为是此时是第一次触发,所以标记位是默认值(安全的)。那么jvm就试着开始一次ygc了。在ygc的过程中,young gen发现此时老年代放不下年轻代晋升的对象,就直接return,不进行下一步的操作了。方法return之后,走到第3步分配。分配失败然后看看能否扩堆。发现都不行,再执行第4步进行一次full gc(同时清除了晋升不安全的标记),完了之后再分配。 这样就走完了一次完整的分配失败触发GC的过程。
以下是对此部分流程的图解。图片比较长,分成2部分….
下一篇文章将写一下图中的进行young gc和进行full gc的具体流程。
从零开始看源码,旨在从源码验证书上的结论,探索书上未知的细节。有疑问欢迎留言探讨
个人源码地址:https://github.com/FlashLightNing/openjdk-notes
还有一个openjdk6,7,8,9的地址:https://github.com/dmlloyd/openjdk
jvm源码阅读笔记[1]:如何触发一次CMS回收
jvm源码阅读笔记[2]:你不知道的晋升阈值TenuringThreshold详解
jvm源码阅读笔记[3]:从内存分配到触发GC的细节
jvm源码阅读笔记[4]:从GC说到vm operation
jvm源码阅读笔记[5]:内存分配失败触发的GC究竟对内存做了什么?
相关文章推荐
- jvm源码阅读笔记[3]:从内存分配到触发GC的细节
- jvm源码阅读笔记[7]-从jstat -gccause命令谈到jvm中都有哪些GC cause
- jvm源码阅读笔记[1]:如何触发一次CMS回收
- jvm源码阅读笔记[4]:从GC说到vm operation
- JVM 初探:内存分配、GC 原理与垃圾收集器
- 深入JVM系列之内存模型与内存分配及 AndroidGC原理
- JVM阅读笔记之Java内存区域与对象的创建
- JVM 学习笔记(三) 垃圾收集器与内存分配策略
- JVM内存分配与GC
- jvm源码阅读笔记[6]-杂谈JIT中对Exception做的优化
- JVM笔记2_垃圾收集器与内存分配策略
- 非典型2D游戏引擎 Orx 源码阅读笔记(3) 内存管理
- JVM的GC中对象的age以及JVM内存的分配策略
- JVM笔记(四)内存分配与回收策略
- jvm学习笔记二(java内存分配策略和回收策略)
- 《Thinking in Java 》学习笔记 --- Java内存分配,对象存放到什么地方?怎么放?
- JVM中的内存分配及GC回收过程简单整理
- JVM学习笔记三:垃圾收集器与内存分配策略
- Java(六) JVM内存分配策略和GC