jvm(10) g1触发gc方法-源码

在initialize_heap_sizes中初始化了gc的线程,后续的gc以任务的形式提交
主要有三种:
1.触发gc操作一般在分配内存时,空间不足可能要oom时才寻找合适的stw时机
(某面试官说道:有人的地方就有江湖,有分配内存的地方都可能oom)
2.system.gc()
3.定时任务G1PeriodicGCTask
想看gc方法找gcRoots什么的需要一个切入点,定时任务作为一个保底的触发机制,可以不断尝试触发gc

先看简单的定时任务

void G1PeriodicGCTask::execute() {
  check_for_periodic_gc();
  // G1PeriodicGCInterval is a manageable flag and can be updated
  // during runtime. If no value is set, wait a second and run it
  // again to see if the value has been updated. Otherwise use the
  // real value provided.
  //这个意思就是如果后续运行过程中进行了参数调整,可以实时调整
  schedule(G1PeriodicGCInterval == 0 ? 1000 : G1PeriodicGCInterval);
}
void ConcurrentGCThread::run() {
  // Setup handle area
  set_active_handles(JNIHandleBlock::allocate_block());

  // Wait for initialization to complete
  wait_init_completed();

  run_service();

  // Signal thread has terminated
  MonitorLocker ml(Terminator_lock);
  Atomic::release_store(&_has_terminated, true);
  ml.notify_all();
}

定时任务提交给了G1ServiceThread,这个类继承到最上面是一个javaThread,和java一样看run方法就行,最后调用到了task中的execute,
在执行check_for_periodic_gc()完成后设置下一次的定时任务

void G1PeriodicGCTask::check_for_periodic_gc() {
  // If disabled, just return.
  // 间隔设置成0后将不会执行gc,直接开启下一秒的gc任务
  if (G1PeriodicGCInterval == 0) {
    return;
  }
  //try_collect就是检查过程,真的要gc的话看里面
  log_debug(gc, periodic)("Checking for periodic GC.");
  if (should_start_periodic_gc()) {
    if (!G1CollectedHeap::heap()->try_collect(GCCause::_g1_periodic_collection)) {
      log_debug(gc, periodic)("GC request denied. Skipping.");
    }
  }
}

这里的GCCause::_g1_periodic_collection为25

bool G1CollectedHeap::try_collect(GCCause::Cause cause) {
  //堆锁判断是否是当前线程的锁
  assert_heap_not_locked();

  // Lock to get consistent set of values.
  uint gc_count_before;
  uint full_gc_count_before;
  uint old_marking_started_before;
  {
    MutexLocker ml(Heap_lock);
    gc_count_before = total_collections();
    full_gc_count_before = total_full_collections();
    old_marking_started_before = _old_marking_cycles_started;
  }
  //判断是否触发并发的full gc
  if (should_do_concurrent_full_gc(cause)) {
    return try_collect_concurrently(cause,
                                    gc_count_before,
                                    old_marking_started_before);
  } else if (GCLocker::should_discard(cause, gc_count_before)) {
  //判断是否是初始化gc并且获取的gc次数被修改过
    // Indicate failure to be consistent with VMOp failure due to
    // another collection slipping in after our gc_count but before
    // our request is processed.
    return false;
  } else if (cause == GCCause::_gc_locker || cause == GCCause::_wb_young_gc
             DEBUG_ONLY(|| cause == GCCause::_scavenge_alot)) {
  //白盒子测试或者初始化
    // Schedule a standard evacuation pause. We're setting word_size
    // to 0 which means that we are not requesting a post-GC allocation.
    VM_G1CollectForAllocation op(0,     /* word_size */
                                 gc_count_before,
                                 cause,
                                 policy()->max_pause_time_ms());
    VMThread::execute(&op);
    return op.gc_succeeded();
  } else {
    // Schedule a Full GC.
    //简单的full gc
    VM_G1CollectFull op(gc_count_before, full_gc_count_before, cause);
    VMThread::execute(&op);
    return op.gc_succeeded();
  }
}

gc有多少种cause可以查看GCCause.cpp

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 _g1_inc_collection_pause:
  return "G1 Evacuation Pause";

通过这个参数可以统计出整个jvm已经进行的gc类型,比如jstat -gccause <pid> 100 (意思是每秒打印gc信息)
其中有一个字段LGCC对应这个原因

C:\Users\tango>jstat -gccause 21136 1000
  S0     S1     E      O      M     CCS    YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT    LGCC                 GCC
  0.00  97.40  10.99  64.32  99.07  95.76     54     0.921     0     0.000     6     0.013     0.934 G1 Evacuation Pause  No GC
  0.00  97.40  10.99  64.32  99.07  95.76     54     0.921     0     0.000     6     0.013     0.934 G1 Evacuation Pause  No GC
  0.00  97.40  10.99  64.32  99.07  95.76     54     0.921     0     0.000     6     0.013     0.934 G1 Evacuation Pause  No GC
  0.00  97.40  10.99  64.32  99.07  95.76     54     0.921     0     0.000     6     0.013     0.934 G1 Evacuation Pause  No GC

jvm内存触发

结合前面内存分配的过程就能快速找到

tlab触发##

开启tlab后,如果内存不够走tlab_slow分配会调用这里,注意这里需要分配的东西是tlab的空间,与后续heap直接分配的方法一样但是对象不一样

HeapWord* G1CollectedHeap::allocate_new_tlab(size_t min_size,
                                             size_t requested_size,
                                             size_t* actual_size) {
//safepoint就是安全点,jvm线程执行字节码自己能够知道
  assert_heap_not_locked_and_not_at_safepoint();
//大对象直接走的heap,tlab不走
  assert(!is_humongous(requested_size), "we do not allow humongous TLABs");

  return attempt_allocation(min_size, requested_size, actual_size);
}

attempt_allocation和heap触发的一样

heap触发##

HeapWord* MemAllocator::allocate_outside_tlab(Allocation& allocation) const {
  allocation._allocated_outside_tlab = true;
  HeapWord* mem = Universe::heap()->mem_allocate(_word_size, &allocation._overhead_limit_exceeded);
  if (mem == NULL) {
    return mem;
  }

  NOT_PRODUCT(Universe::heap()->check_for_non_bad_heap_word_value(mem, _word_size));
  size_t size_in_bytes = _word_size * HeapWordSize;
  _thread->incr_allocated_bytes(size_in_bytes);

  return mem;
}

mem_allocate

HeapWord*
G1CollectedHeap::mem_allocate(size_t word_size,
                              bool*  gc_overhead_limit_was_exceeded) {
  assert_heap_not_locked_and_not_at_safepoint();
 
  if (is_humongous(word_size)) {
    return attempt_allocation_humongous(word_size);
  }
  size_t dummy = 0;
//和tlab_slow一个方法
  return attempt_allocation(word_size, word_size, &dummy);
}

attempt_allocation_humongous
里面可以触发gc原因的有
_g1_humongous_allocation
_g1_preventive_collection
特别注意大对象触发时,由于大对象消耗内存很快,所以会触发检查是否要进行并发标记

// Humongous objects can exhaust the heap quickly, so we should check if we
  // need to start a marking cycle at each humongous object allocation. We do
  // the check before we do the actual allocation. The reason for doing it
  // before the allocation is that we avoid having to keep track of the newly
  // allocated memory while we do a GC.
  if (policy()->need_to_start_conc_mark("concurrent humongous allocation",
                                        word_size)) {
    collect(GCCause::_g1_humongous_allocation);
  }

attempt_allocation

inline HeapWord* G1CollectedHeap::attempt_allocation(size_t min_word_size,
                                                     size_t desired_word_size,
                                                     size_t* actual_word_size) {
  assert_heap_not_locked_and_not_at_safepoint();
  assert(!is_humongous(desired_word_size), "attempt_allocation() should not "
         "be called for humongous allocation requests");
//numa从系统的多线程空间获取内存
  HeapWord* result = _allocator->attempt_allocation(min_word_size, desired_word_size, actual_word_size);
//numa分配失败
  if (result == NULL) {
    *actual_word_size = desired_word_size;
//内部可能触发gc再进行内存分配
    result = attempt_allocation_slow(desired_word_size);
  }

  assert_heap_not_locked();
  if (result != NULL) {
    assert(*actual_word_size != 0, "Actual size must have been set here");
    dirty_young_block(result, *actual_word_size);
  } else {
    *actual_word_size = 0;
  }

  return result;
}

system.gc()

JVM_ENTRY_NO_ENV(void, JVM_GC(void))
  if (!DisableExplicitGC) {
    EventSystemGC event;
    event.set_invokedConcurrent(ExplicitGCInvokesConcurrent);
    Universe::heap()->collect(GCCause::_java_lang_system_gc);
    event.commit();
  }
JVM_END
//collect
void G1CollectedHeap::collect(GCCause::Cause cause) {
  try_collect(cause);
}

回到了开始的定时任务,而且人家还是一秒一次的
如果版本支持G1PeriodicGCTask这个system.gc()就不要写了吧

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容