預設情況下,并不是等堆記憶體耗盡,才會報 OutOfMemoryError,而是如果 JVM 覺得 GC 效率不高,也會報這個錯誤。
那麼怎麼評價 GC 效率不高呢?來看下源碼:
呢?來看下源碼gcOverheadChecker.cpp:
void GCOverheadChecker::check_gc_overhead_limit(GCOverheadTester* time_overhead,
GCOverheadTester* space_overhead,
bool is_full_gc,
GCCause::Cause gc_cause,
SoftRefPolicy* soft_ref_policy) {
// 忽略顯式gc指令,比如System.gc(),或者通過JVMTI指令的gc,或者通過jcmd指令的gc
if (GCCause::is_user_requested_gc(gc_cause) ||
GCCause::is_serviceability_requested_gc(gc_cause)) {
return;
}
bool print_gc_overhead_limit_would_be_exceeded = false;
if (is_full_gc) {
//如果gc時間過長,并且gc回收的空間還是不多
//gc時間占用98%以上為gc時間過長,可以通過 -XX:GCTimeLimit= 配置,參考gc_globals.hpp: GCTimeLimit
//回收空間小于2%為gc回收空間不多,可以通過 -XX:GCHeapFreeLimit= 配置,參考gc_globals.hpp: GCHeapFreeLimit
if (time_overhead->is_exceeded() && space_overhead->is_exceeded()) {
_gc_overhead_limit_count++;
//如果UseGCOverheadLimit這個狀态位為開啟
//預設情況下,是開啟的,可以通過啟動參數-XX:-UseGCOverheadLimit關閉,參考:gc_globals.hpp: UseGCOverheadLimit
if (UseGCOverheadLimit) {
//如果超過規定次數,這個次數預設不可配置,必須開啟develop編譯jdk才能配置,參考gc_globals.hpp: GCOverheadLimitThreshold
if (_gc_overhead_limit_count >= GCOverheadLimitThreshold){
//設定狀态位,準備抛出OOM
set_gc_overhead_limit_exceeded(true);
//清空計數
reset_gc_overhead_limit_count();
} else {
//如果還沒到達次數,但是也快到達的時候,清空所有的軟引用
bool near_limit = gc_overhead_limit_near();
if (near_limit) {
soft_ref_policy->set_should_clear_all_soft_refs(true);
log_trace(gc, ergo)("Nearing GC overhead limit, will be clearing all SoftReference");
}
}
}
//需要列印日志,提示GC效率不高
print_gc_overhead_limit_would_be_exceeded = true;
} else {
// Did not exceed overhead limits
reset_gc_overhead_limit_count();
}
}
if (UseGCOverheadLimit) {
if (gc_overhead_limit_exceeded()) {
log_trace(gc, ergo)("GC is exceeding overhead limit of " UINTX_FORMAT "%%", GCTimeLimit);
reset_gc_overhead_limit_count();
} else if (print_gc_overhead_limit_would_be_exceeded) {
assert(_gc_overhead_limit_count > 0, "Should not be printing");
log_trace(gc, ergo)("GC would exceed overhead limit of " UINTX_FORMAT "%% %d consecutive time(s)",
GCTimeLimit, _gc_overhead_limit_count);
}
}
}
預設配置:gc_globals.hpp
product(bool, UseGCOverheadLimit, true, \
"Use policy to limit of proportion of time spent in GC " \
"before an OutOfMemory error is thrown") \
\
product(uintx, GCTimeLimit, 98, \
"Limit of the proportion of time spent in GC before " \
"an OutOfMemoryError is thrown (used with GCHeapFreeLimit)") \
range(0, 100) \
\
product(uintx, GCHeapFreeLimit, 2, \
"Minimum percentage of free space after a full GC before an " \
"OutOfMemoryError is thrown (used with GCTimeLimit)") \
range(0, 100) \
\
develop(uintx, GCOverheadLimitThreshold, 5, \
"Number of consecutive collections before gc time limit fires") \
range(1, max_uintx)
可以總結出:預設情況下,啟用了 UseGCOverheadLimit,連續 5 次,碰到 GC 時間占比超過 98%,GC 回收的記憶體不足 2% 時,會抛出這個異常。
微信搜尋“我的程式設計喵”關注公衆号,每日一刷,輕松提升技術,斬獲各種offer: