JVM垃圾回收的時機是什么時候(深入理解 JVM 垃圾回收時機:什么時候會觸發 GC?)

深入理解 JVM 垃圾回收時機:什么時候會觸發 GC?

在 Java 開發中,我們常聽說 “JVM 會自動進行垃圾回收”,但很少有人能說清:GC 究竟在什么情況下會被觸發?是到固定時間就執行?還是內存滿了才會啟動?其實,JVM 的垃圾回收時機并非 “一刀切”,而是由內存狀態、GC 算法策略和用戶配置共同決定的動態行為。今天我們就從實際場景出發,拆解 GC 的觸發機制。

一、最常見的觸發場景:內存不足了

當 JVM 無法為新對象分配內存時,會 “被動” 觸發垃圾回收 —— 這是 GC 最核心、最頻繁的觸發原因,也是我們日常開發中最需要關注的場景。根據內存區域的不同,又可分為三類:

1. 新生代內存不足:觸發 Minor GC(Young 分為三類:

1. 新生代內存不足:觸發 Minor GC(Young GC)

新生代是 Java 對象的 “出生地”,絕大多數新創建的對象(如new User()、new int[10])都會先分配到新生代的 Eden 區。由于新生代空間通常較小(比如幾十到幾百 MB,通過-Xmn配置),Eden 區很容易被填滿。

觸發邏輯

當 Eden 區滿了,JVM 無法為新對象分配內存時,會立即觸發Minor GC—— 只針對新生代(Eden 區 + 兩個 Survivor 區)進行回收,清理掉 “無用對象”(沒有任何引用指向的對象)。

舉個例子

假設 Eden 區大小為 100MB,我們循環創建 1000 個 100KB 的對象,當創建到第 1001 個時,Eden 區已被占滿,JVM 會觸發 Minor GC,回收掉其中已無引用的對象(比如前 500 個已被賦值為null的對象),釋放空間后繼續分配新對象。

特點

  • 頻率高(可能每秒觸發多次);
  • 耗時短(新生代對象存活時間短,大部分對象會被回收,且 GC 過程中只有部分階段會暫停用戶線程);
  • 不會影響老年代(Minor GC 只處理新生代)。

2. 老年代內存不足:觸發 Major GC/Full GC

老年代存儲的是 “存活時間長” 的對象 —— 比如頻繁被引用的單例對象、從新生代多次 Minor GC 后存活下來的對象(默認存活 15 次 Minor GC 后會晉升到老年代)。當老年代空間不足時,會觸發更 “重量級” 的回收。

觸發邏輯

有兩種典型情況會導致老年代內存不足:

  1. 新生代對象晉升到老年代時,發現老年代空間不夠(比如一個大對象從 Eden 區直接晉升,而老年代剩余空間不足);
  1. 老年代自身的對象積累過多,可用空間低于閾值。

此時 JVM 會觸發Major GC—— 主要回收老年代的無用對象,部分情況下會同時回收新生代(這種跨區域的回收稱為 Full GC)。

注意

如果 Major GC 后,老年代仍無法釋放足夠空間,JVM 會拋出OutOfMemoryError: Java heap space—— 這是我們常遇到的 “內存溢出” 錯誤,需要通過調整堆大小(-Xmx)或優化對象生命周期來解決。

特點

  • 頻率低(可能幾分鐘甚至幾小時觸發一次);
  • 耗時長(老年代對象存活時間長,需要更復雜的掃描和判斷,且 Full GC 會導致 “Stop The World”(STW)—— 暫停所有用戶線程,可能造成業務卡頓)。

3. 方法區(元空間)內存不足:觸發元空間 GC

JDK 8 之后,方法區的實現從 “永久代” 改為 “元空間”,主要存儲類的元信息(如類名、字段、方法代碼)、常量池和靜態變量。元空間默認使用 “本地內存”(不受 JVM 堆大小限制),但并非無限 —— 當元空間內存不足時,也會觸發 GC。

觸發邏輯

當動態生成大量類(比如使用 CGLIB 代理、反射生成類),導致元空間存儲的類信息過多,超過了系統可用的本地內存時,JVM 會觸發元空間的 GC,清理掉 “不再使用的類”(比如類加載器已被回收、類的所有實例已被回收)。

如果回收后仍不足

JVM 會拋出OutOfMemoryError: Metaspace—— 這種錯誤常見于頻繁動態生成類的場景(如某些 ORM 框架、動態代理框架使用不當)。

二、主動觸發:GC 算法的 “策略性回收”

除了 “內存不足” 這種被動情況,JVM 也會根據 GC 算法的預設策略,在內存暫時充足時 “主動” 觸發 GC,目的是避免內存過度占用后集中回收導致的性能波動。

1. 定時掃描:并發 GC 的后臺工作

對于支持 “并發回收” 的 GC 算法(如 CMS、G1),JVM 會啟動專門的 “后臺回收線程”,定期掃描內存區域(比如每幾秒一次),主動尋找無用對象并回收。這種方式可以 “見縫插針” 地釋放內存,減少 Full GC 的頻率。

比如 CMS 算法的 “并發標記” 階段,后臺線程會在用戶線程運行的同時,悄悄掃描老年代的對象引用,標記出無用對象,后續再通過短時間的 STW 階段完成回收 —— 整個過程對業務的影響很小。

2. 大對象分配前的 “預判回收”

JVM 對 “大對象”(比如超過 Eden 區一半大小的數組、大字符串)有特殊處理邏輯:為了避免大對象直接進入老年代(可能快速耗盡老年代空間),在分配大對象前,JVM 會先觸發一次 Minor GC,嘗試釋放 Eden 區的空間。如果釋放后仍無法容納大對象,才會將其直接分配到老年代。

舉個例子

Eden 區大小為 100MB,我們要創建一個 60MB 的數組(屬于大對象)。此時 JVM 會先觸發 Minor GC,回收 Eden 區中無用的對象(假設釋放了 40MB 空間),但 Eden 區剩余空間(40MB)仍不足以容納 60MB 的數組,最終會將數組直接分配到老年代。

3. 內存使用率達到閾值:提前預防

部分 GC 算法支持通過參數配置 “內存使用率閾值”,當內存使用率達到閾值時,主動觸發 GC,避免內存被完全占滿。

最典型的是 CMS 算法的-XX:CMSInitiatingOccupancyFraction參數:默認值為 92,表示當老年代使用率達到 92% 時,會主動觸發 CMS 回收。如果不提前觸發,等到老年代滿了再回收,可能會被迫執行 “Serial Old GC”(一種更慢的 Full GC),導致更長時間的 STW。

三、不推薦的方式:手動觸發 GC

Java 提供了手動 “建議” JVM 執行 GC 的 API,但請注意:這只是建議,不是強制 ——JVM 可以忽略你的請求。

// 兩種手動觸發GC的方式(效果完全相同)System.gc();Runtime.getRuntime().gc();

為什么不推薦?

  1. 破壞 JVM 的自動優化:JVM 會根據內存狀態動態調整 GC 時機,手動觸發可能打亂其優化策略(比如明明內存還充足,卻強制觸發 Full GC,導致性能浪費);
  1. 可能導致業務卡頓:手動調用System.gc()大概率會觸發 Full GC,造成 STW,影響用戶體驗;
  1. 無法解決根本問題:如果頻繁需要手動觸發 GC,說明代碼存在內存泄漏或對象生命周期設計不合理,應該優化代碼而非依賴手動 GC。

例外場景

僅在測試環境(如驗證內存泄漏是否修復)或工具類(如 JVM 監控工具)中,才可能偶爾使用手動 GC,生產環境絕對禁止。

四、特殊場景:JVM 退出或動態擴容時

除了上述常規場景,還有兩種特殊情況會觸發 GC:

1. JVM 進程退出前

當 JVM 進程即將退出時(比如執行System.exit(0)、程序正常結束),會觸發一次 Full GC—— 但此時回收內存已無實際意義,更多是 JVM 的 “清理流程”,確保資源正常釋放。

2. 堆內存動態擴容時

JVM 堆內存支持動態擴容(默認開啟,通過-Xms設置初始大小,-Xmx設置最大大小)。當堆內存從初始大小向最大大小擴容時,如果擴容后的空間仍不足以分配新對象,會觸發 GC,嘗試釋放內存后再繼續擴容。

總結:GC 時機的核心原則

JVM 垃圾回收的觸發時機,本質是 “按需觸發,策略輔助”:

  1. 核心驅動力:內存不足(新生代滿、老年代滿、元空間滿)—— 這是 GC 最根本的觸發原因;
  1. 優化策略:定時掃描、大對象預判、閾值觸發 —— 這些是為了減少 Full GC 頻率,提升性能;
  1. 禁止手動干預:手動觸發 GC 會破壞 JVM 的自動優化,生產環境絕對避免。

理解 GC 的觸發機制,能幫助我們更好地排查內存問題:比如遇到頻繁 Minor GC,可能是新生代空間太小;遇到頻繁 Full GC,可能是老年代有內存泄漏或大對象過多。后續我們還會深入講解不同 GC 算法的具體實現,敬請關注!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/99137.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/99137.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/99137.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在Vue項目中Axios發起請求時的小知識

在Vue項目中Axios發起請求時的小知識 在Vue項目開發中,Axios作為基于Promise的HTTP客戶端,憑借其簡潔的API設計和強大的功能(如請求/響應攔截、自動JSON轉換、取消請求等),已成為前端與后端通信的主流選擇。本文將深入…

GeoHash分級索引技術

GeoHash分級索引技術是一種將二維地理坐標轉換為一維字符串的空間索引方法,其核心是通過分級網格劃分和前綴編碼實現高效的空間數據檢索。以下從技術原理、實現細節到工程優化展開詳細解析: 一、編碼原理與分級結構 1. 經緯度二進制化 GeoHash通過遞歸二分地球表面生成網格…

HTML HTML基礎(4)

1.列表 (1).有序列表 概念&#xff1a;有順序或側重順序的列表。 <h2>要把大象放冰箱總共分幾步</h2> <ol> <li>把冰箱門打開</li> <li>把大象放進去</li> <li>把冰箱門關上</li> </ol> (2).無序列表 概念&a…

MySQL中的回表操作

在數據庫查詢&#xff08;尤其是基于 B樹索引 的關系型數據庫&#xff0c;如MySQL、PostgreSQL&#xff09;中&#xff0c;“回表”是一個核心且高頻出現的概念&#xff0c;直接影響查詢性能。要理解回表&#xff0c;需先理清索引結構與數據存儲的關聯&#xff0c;再拆解其發生…

QT子線程與GUI線程安全交互

在Qt應用程序開發中&#xff0c;涉及到多線程處理時&#xff0c;如何安全地從子線程更新UI界面是一個常見的問題。Qt的UI界面并不是線程安全的&#xff0c;意味著你不能直接在子線程中操作UI組件&#xff08;比如按鈕、標簽等&#xff09;。如果不遵循線程安全的規則&#xff0…

RL【10-2】:Actor - Critic

系列文章目錄 Fundamental Tools RL【1】&#xff1a;Basic Concepts RL【2】&#xff1a;Bellman Equation RL【3】&#xff1a;Bellman Optimality Equation Algorithm RL【4】&#xff1a;Value Iteration and Policy Iteration RL【5】&#xff1a;Monte Carlo Learnin…

開源大模型天花板?DeepSeek-V3 6710億參數MoE架構深度拆解

文章目錄認知解構&#xff1a;DeepSeek的定位與核心價值模型概述與發展歷程創立初期與技術奠基&#xff08;2023年7月-2024年11月&#xff09;里程碑一&#xff1a;MoE架構規模化突破&#xff08;2024年12月&#xff09;里程碑二&#xff1a;推理成本革命性優化&#xff08;202…

10 訓練中的一些問題

&#x1f31f; 大背景&#xff1a;訓練神經網絡 下山尋寶 訓練神經網絡就像你蒙著眼在一座大山里&#xff0c;想找最低點&#xff08;最小損失&#xff09;。你只能靠腳下的坡度&#xff08;梯度&#xff09;來決定往哪兒走。 你的位置 模型參數&#xff08;權重 www&#xf…

synchronized鎖升級的過程(從無鎖到偏向鎖,再到輕量級鎖,最后到重量級鎖的一個過程)

鎖升級是 Java 中 synchronized 鎖 的核心優化機制&#xff08;基于 JVM 的 對象頭 Mark Word 實現&#xff09;&#xff0c;指鎖的狀態從 無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖 逐步升級的過程。其目的是通過 “按需升級”&#xff0c;在不同并發場景下選擇最優的鎖實現&am…

HOT100--Day25--84. 柱狀圖中最大的矩形,215. 數組中的第K個最大元素,347. 前 K 個高頻元素

HOT100–Day25–84. 柱狀圖中最大的矩形&#xff0c;215. 數組中的第K個最大元素&#xff0c;347. 前 K 個高頻元素 每日刷題系列。今天的題目是《力扣HOT100》題單。 題目類型&#xff1a;棧&#xff0c;堆。 84. 柱狀圖中最大的矩形 思路&#xff1a; class Solution {publ…

基于 Apache Doris 的用戶畫像數據模型設計方案

一、 需求分析與設計目標數據源&#xff1a;用戶基本信息&#xff1a;用戶ID、性別、出生日期、注冊時間、常駐地域&#xff08;省、市、區&#xff09;、職業等。用戶體檢報告&#xff1a;每次體檢的報告ID、體檢時間、各項指標&#xff08;如血壓、血糖、血脂、BMI等&#xf…

Python的深度學習

深入理解Python高級特性掌握Python的高級特性是進階的關鍵&#xff0c;包括裝飾器、生成器、上下文管理器、元類等。這些特性能夠提升代碼的靈活性和效率。例如&#xff0c;裝飾器可以用于實現AOP&#xff08;面向切面編程&#xff09;&#xff0c;生成器可以處理大數據流而無需…

數據庫范式(Normalization)

一個設計混亂的數據庫就像一個雜亂的房間&#xff0c;用起來非常不方便&#xff1a;東西到處亂放&#xff08;數據冗余&#xff09;&#xff0c;找件東西要翻遍所有角落&#xff08;查詢困難&#xff09;&#xff0c;扔掉一把舊椅子時&#xff0c;可能會把搭在上面的唯一一件外…

數據結構---循環隊列

基于循環數組實現的循環隊列解決了順序隊列中的假溢出導致的空間浪費問題操作&#xff1a;&#xff08;1&#xff09;初始化//循環隊列 typedef struct {int *data;//指針模擬聲明數組int head,tail;//隊頭&#xff0c;隊尾 }Queue; //初始化 Queue *InitQueue() {Queue *q (Q…

深入理解線程模型

線程作為操作系統調度的基本執行單元&#xff0c;是實現高吞吐、低延遲系統的基礎。一、進程與線程的體系結構對比核心概念&#xff1a;進程&#xff08;Process&#xff09;&#xff1a;操作系統資源分配的基本單位&#xff0c;擁有獨立的虛擬地址空間、文件描述符表、環境變量…

TTC定時器中斷——MPSOC實戰3

開啟TTC定時器&#xff0c;不同于7000系列的私有定時器此處設置LPD_LSBUS頻率TTC頻率取決于LPD_LSBUS可前往指定位置查看參數不使能填寫對應宏可前往指定位置查看參數main.c#include <stdio.h> #include "xparameters.h" #include "xgpiops.h" #incl…

人工智能訓練師三級備考筆記

一、實操1&#xff09;通用語法&#xff08;常見于實操題第一塊代碼塊&#xff09;1.讀取文件數據或加載數據集等描述時一般為以下結構&#xff1a;Datapd.read_文件格式(文件名) 注意&#xff1a;文件名需要用‘ ’框起來&#xff0c;必須要有引號文件格式有以下內容csv、txt…

Cherry Studio遞歸工具調用機制深度解析

在現代AI應用開發中,工具調用(Tool Calling)已成為大語言模型與外部系統交互的核心機制。Cherry Studio作為一款先進的AI對話客戶端,實現了一套完整的遞歸工具調用系統,能夠讓AI助手在執行復雜任務時自動調用多個工具,并根據執行結果智能決策下一步操作。本文將深入解析這…

[哈希表]966. 元音拼寫檢查器

966. 元音拼寫檢查器 class Solution:def spellchecker(self, wordlist: List[str], queries: List[str]) -> List[str]:origin set(wordlist) # 存儲原始單詞用于完全匹配lower_to_origin {} # 存儲小寫形式到原始單詞的映射vowel_to_origin {} # 存儲元音模糊形…

正則表達式與文本三劍客(grep、sed、awk)基礎與實踐

正則表達式基礎與實踐一、正則表達式概述1. 定義正則表達式&#xff08;Regular Expression&#xff0c;簡稱 RE&#xff09;是用于描述字符排列和匹配模式的語法規則&#xff0c;核心作用是對字符串進行分割、匹配、查找、替換操作。它本質是 “模式模板”&#xff0c;Linux 工…