前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >读懂一行Full GC日志(回复JVM内存分配担保机制一文中 Mr/Mrs Xxx 在留言区提出的问题)

读懂一行Full GC日志(回复JVM内存分配担保机制一文中 Mr/Mrs Xxx 在留言区提出的问题)

作者头像
ImportSource
发布2018-04-03 11:59:14
10.3K2
发布2018-04-03 11:59:14
举报
文章被收录于专栏:ImportSourceImportSource

回复JVM内存分配担保机制一文中 Mr/Mrs Xxx 在留言区提出的问题:

“请问分配3M的时候,怎么还发生了full gc?”

回复如下:

发生Full GC,有很多种原因,不仅仅是只有Allocation Failure。

还有以下这么多:

代码语言:javascript
复制
#include "precompiled.hpp"
#include "gc/shared/gcCause.hpp"

const char* GCCause::to_string(GCCause::Cause cause) {
  switch (cause) {
    case _java_lang_system_gc:
      return "System.gc()";

    case _full_gc_alot:
      return "FullGCAlot";

    case _scavenge_alot:
      return "ScavengeAlot";

    case _allocation_profiler:
      return "Allocation Profiler";

    case _jvmti_force_gc:
      return "JvmtiEnv ForceGarbageCollection";

    case _gc_locker:
      return "GCLocker Initiated GC";

    case _heap_inspection:
      return "Heap Inspection Initiated GC";

    case _heap_dump:
      return "Heap Dump Initiated GC";

    case _wb_young_gc:
      return "WhiteBox Initiated Young GC";

    case _wb_conc_mark:
      return "WhiteBox Initiated Concurrent Mark";

    case _wb_full_gc:
      return "WhiteBox Initiated Full GC";

    case _update_allocation_context_stats_inc:
    case _update_allocation_context_stats_full:
      return "Update Allocation Context Stats";

    case _no_gc:
      return "No GC";

    case _allocation_failure:
      return "Allocation Failure";

    case _tenured_generation_full:
      return "Tenured Generation Full";

    case _metadata_GC_threshold:
      return "Metadata GC Threshold";

    case _metadata_GC_clear_soft_refs:
      return "Metadata GC Clear Soft References";

    case _cms_generation_full:
      return "CMS Generation Full";

    case _cms_initial_mark:
      return "CMS Initial Mark";

    case _cms_final_remark:
      return "CMS Final Remark";

    case _cms_concurrent_mark:
      return "CMS Concurrent Mark";

    case _old_generation_expanded_on_last_scavenge:
      return "Old Generation Expanded On Last Scavenge";

    case _old_generation_too_full_to_scavenge:
      return "Old Generation Too Full To Scavenge";

    case _adaptive_size_policy:
      return "Ergonomics";

    case _g1_inc_collection_pause:
      return "G1 Evacuation Pause";

    case _g1_humongous_allocation:
      return "G1 Humongous Allocation";

    case _dcmd_gc_run:
      return "Diagnostic Command";

    case _last_gc_cause:
      return "ILLEGAL VALUE - last gc cause - ILLEGAL VALUE";

    default:
      return "unknown GCCause";
  }
  ShouldNotReachHere();
}

该文JVM内存分配担保机制在后面部分讲到在Server模式下,当设置为3M的时候,偶尔会发生Full GC。注意:是“偶尔”。

另外我们看到日志片段:

[Full GC (Ergonomics) [PSYoungGen: 544K->0K(9216K)] [ParOldGen: 6144K->6627K(10240K)] 6688K->6627K(19456K), [Metaspace: 3286K->3286K(1056768K)], 0.0063048 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

发现Full GC后面还有一个单词叫Ergonomics,Full GC后面的括号就是本次GC所产生的原因。

上文中我们说到:

发现当我们使用Server模式下的ParallelGC收集器组合(Parallel Scavenge+Serial Old的组合)下,担保机制的实现和之前的Client模式下(SerialGC收集器组合)有所变化。在GC前还会进行一次判断,如果要分配的内存>=Eden区大小的一半,那么会直接把要分配的内存放入老年代中。否则才会进入担保机制。

也就是使用了Parallel Scavenge+Serial Old的组合。

我们就去看看Parallel Scavenge回收策略的源码吧!

以下是片段:

代码语言:javascript
复制
// This method contains all heap specific policy for invoking scavenge.
// PSScavenge::invoke_no_policy() will do nothing but attempt to
// scavenge. It will not clean up after failed promotions, bail out if
// we've exceeded policy time limits, or any other special behavior.
// All such policy should be placed here.
//
// Note that this method should only be called from the vm_thread while
// at a safepoint!
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(!ParallelScavengeHeap::heap()->is_gc_active(), "not reentrant");

  ParallelScavengeHeap* const heap = ParallelScavengeHeap::heap();
  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;
}

核心代码:

代码语言:javascript
复制
 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);
    }
  }  

注:基本内容是如果需要full gc那么就进入if块,然后执行full gc逻辑。另外这里的_adaptive_size_policy 常量就是对应的Ergonomics:

代码语言:javascript
复制

代码语言:javascript
复制
 case _adaptive_size_policy:
      return "Ergonomics";

那么full gc的条件是什么呢?也就是什么情况导致发生了本次full gc呢?

我们继续看看need_full_gc这个常量吧:

full gc条件:

代码语言:javascript
复制
const bool need_full_gc = !scavenge_done ||
  policy->should_full_GC(heap->old_gen()->free_in_bytes());

should_ful_GC方法:

代码语言:javascript
复制
// If the remaining free space in the old generation is less that
// that expected to be needed by the next collection, do a full
// collection now.
bool PSAdaptiveSizePolicy::should_full_GC(size_t old_free_in_bytes) {

  // A similar test is done in the scavenge's should_attempt_scavenge().  If
  // this is changed, decide if that test should also be changed.
  bool result = padded_average_promoted_in_bytes() > (float) old_free_in_bytes;
  //如果晋升到老年代的平均大小大于老年代的剩余大小,则认为要进行一次full gc
  
  log_trace(gc, ergo)(
  "%s after scavenge average_promoted " 
SIZE_FORMAT " padded_average_promoted " 
SIZE_FORMAT " free in old gen " SIZE_FORMAT,
                      result ? "Full" : "No full",
                      (size_t) average_promoted_in_bytes(),
                      (size_t) padded_average_promoted_in_bytes(),
                      old_free_in_bytes);
  return result;
}

通过查看should_full_GC方法,我们发现了这行代码:

代码语言:javascript
复制
bool result = padded_average_promoted_in_bytes() > (float) old_free_in_bytes;

通过该行代码,我们知道,如果晋升到老生代的平均大小大于老生代的剩余大小,则会返回true,认为需要一次full gc。

通过注释也可以知道:

代码语言:javascript
复制
If the remaining free space in the old generation is less than
that expected to be needed by the next collection, do a full
collection now.

如果老生代的剩余空间少于下一次收集所需的剩余空间,那么现在就做一个完整的收集。

如果 padded_average_promoted_in_bytes()大于老生代剩余空间,那么就返回true,表示要触发一次fullgc。

那么padded_average_promoted_in_bytes()这个平均大小是怎么算出来的呢?我们去看看:

代码语言:javascript
复制
// Padded average in bytes
size_t padded_average_promoted_in_bytes() const {
  return (size_t)_avg_promoted->padded_average();
}

float padded_average() const         { return _padded_avg; }
代码语言:javascript
复制
// A weighted average that includes a deviation from the average,
// some multiple of which is added to the average.
//
// This serves as our best estimate of an upper bound on a future
// unknown.
class AdaptivePaddedAverage : public AdaptiveWeightedAverage {
 private:
  float          _padded_avg;     // The last computed padded average
  float          _deviation;      // Running deviation from the average
  unsigned       _padding;        // A multiple which, added to the average,
                                  // gives us an upper bound guess.

 protected:
  void set_padded_average(float avg)  { _padded_avg = avg;  }
  void set_deviation(float dev)       { _deviation  = dev;  }

 public:
  AdaptivePaddedAverage() :
    AdaptiveWeightedAverage(0),
    _padded_avg(0.0), _deviation(0.0), _padding(0) {}

  AdaptivePaddedAverage(unsigned weight, unsigned padding) :
    AdaptiveWeightedAverage(weight),
    _padded_avg(0.0), _deviation(0.0), _padding(padding) {}

  // Placement support
  void* operator new(size_t ignored, void* p) throw() { return p; }
  // Allocator
  void* operator new(size_t size) throw() { return CHeapObj<mtGC>::operator new(size); }

  // Accessor
  float padded_average() const         { return _padded_avg; }
  float deviation()      const         { return _deviation;  }
  unsigned padding()     const         { return _padding;    }

  void clear() {
    AdaptiveWeightedAverage::clear();
    _padded_avg = 0;
    _deviation = 0;
  }

  // Override
  void  sample(float new_sample);

  // Printing
  void print_on(outputStream* st) const;
  void print() const;
};

可以从代码和注释中我们发现:

加权平均值包括与平均值的偏差,其平均值加上其中的一些倍数。 这是对未来未知数的上限的最佳估计。

也就是通过这样的算法,虚拟机估算出下次分配可能会发生无法分配的问题,于是提前预测到可能的问题,提前发生一次full gc

于是这次full gc就发生了!

那么你也许有疑问说[Full GC (Ergonomics) 的Ergonomics究竟是个什么东东?

Ergonomics翻译成中文,一般都是“人体工程学”。在JVM中的垃圾收集器中的Ergonomics就是负责自动的调解gc暂停时间和吞吐量之间的平衡,然后你的虚拟机性能更好的一种做法。

对于注重吞吐量的收集器来说,在某个generation被过渡使用之前,GC ergonomics就会启动一次GC。

正如我们前面提到的,发生本次full gc正是在使用Parallel Scavenge收集器的情况下发生的。

而Parallel Scavenge正是一款注重吞吐量的收集器:

Parallel Scavenge的目标是达到一个可控的吞吐量,吞吐量=程序运行时间/(程序运行时间+GC时间),如程序运行了99s,GC耗时1s,吞吐量=99/(99+1)=99%。Parallel Scavenge提供了两个参数用以精确控制吞吐量,分别是用以控制最大GC停顿时间的-XX:MaxGCPauseMillis及直接控制吞吐量的参数-XX:GCTimeRatio。

好,就到这里吧。

总之,以后遇到Full GC,不一定只有Allocation Failure,还有更多,比如本文中的“Ergonomics”。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-12-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ImportSource 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档