默認情況下,並不是等堆內存耗盡,纔會報 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: