1、如何判斷對象是否死亡(兩種方法)
有兩種方法判定對象死亡:引用計數(shù)法、可達性分析算法
- 引用計數(shù)法:每當有一個地方引用它,計數(shù)器+1。當引用失效,計數(shù)器-1。任何計數(shù)器為 0 的對象都是不再使用的。缺點是無法解決循環(huán)引用問題。
public class ReferenceCountingGc {
Object instance = null;
public static void main(String[] args) {
ReferenceCountingGc objA = new ReferenceCountingGc();
ReferenceCountingGc objB = new ReferenceCountingGc();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
}
- 可達性分析算法:以 "GC Roots"作為起點,向下搜索所走過的路徑稱為引用鏈。當一個對象到 "GC Roots"無任務引用鏈時,則證明此對象不可達,需要被回收。哪些對象可以作為 GC Roots 呢?虛擬機棧(棧幀中的局部變量表)中引用的對象;本地方法棧(Native 方法)中引用的對象;方法區(qū)中類靜態(tài)屬性引用的對象;方法區(qū)中常量引用的對象;所有被同步鎖持有的對象;JNI(Java Native Interface)引用的對象。
2、簡單的介紹一下強引用、軟引用、弱引用、虛引用(虛引用與軟引用和弱引用的區(qū)別、使用軟引用能帶來的好處)
引用的強弱程度,決定了對象被垃圾回收的時機和條件:
- 強引用 (Strong Reference)
Object obj = new Object(); // 強引用
最常見的引用類型,GC 永遠不會回收強引用對象,即使內(nèi)存不足時,寧愿拋 OOM 也不會回收,當 obj = null 時,對象才可能被回收。
- 軟引用 (Soft Reference)
SoftReference<Object> soft = new SoftReference<>(new Object());
內(nèi)存充足時不會被回收,內(nèi)存不足時會被回收,常用于實現(xiàn)內(nèi)存敏感的緩存,可以配合引用隊列使用。
- 弱引用 (Weak Reference)
WeakReference<Object> weak = new WeakReference<>(new Object());
比軟引用更弱,只要發(fā)生 GC 就會被回收,常用于避免內(nèi)存泄漏,WeakHashMap 就是基于弱引用實現(xiàn)。
- 虛引用 (Phantom Reference)
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), queue);
最弱的引用類型,隨時可能被回收,必須和引用隊列配合使用,主要用于跟蹤對象被回收的狀態(tài)。
3、如何判斷一個常量是廢棄常量
假如在字符串常量池中存在字符串 "abc",如果當前沒有任何 String 對象引用該字符串常量的話,就說明常量 "abc" 就是廢棄常量,如果這時發(fā)生內(nèi)存回收的話而且有必要的話,"abc" 就會被系統(tǒng)清理出常量池了。
4、如何判斷一個類是無用的類
判定方法區(qū)一個類是無用類,需要滿足 3 個條件:
- 該類所有的實例都已經(jīng)被回收,也就是 Java 堆中不存在該類的任何實例。
- 加載該類的 ClassLoader 已經(jīng)被回收。
- 該類對應的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
5、垃圾收集有哪些算法,各自的特點?
- 標記-清除(mark-sweep):標記階段,確定所有要回收的對象,并做標記;清除階段,將標記階段確定不可用的對象清除掉。缺點:標記和清除階段效率都不高;會產(chǎn)生大量碎片導致頻繁的回收。
- 復制(copy):內(nèi)存被分為大小相同的兩塊,每次用1 塊,當開始垃圾回收,把存活的對象復制到另一塊,然后這塊內(nèi)容整個清理。缺點:需要浪費額外的內(nèi)存作為復制區(qū);對于存活率較高,復制算法效率會降低。
- 標記-整理(mark-compact):不是把存活的對象復制到另一塊內(nèi)存,而是把存活對象往內(nèi)存一段移動,然后直接回收邊界以外的內(nèi)存。缺點:移動過程效率較低。
6、HotSpot 為什么要分為新生代和老年代
- 對象生命周期差異,大多數(shù)對象生命周期短(如臨時計算對象),僅需一次Minor GC即可回收;少數(shù)對象存活時間長(如全局配置對象),需多次Minor GC后晉升至老年代。分代策略通過區(qū)分對象生命周期,優(yōu)化垃圾回收效率。
- 垃圾回收算法適配性,新生代:采用復制算法,利用短命對象占比高的特性,將存活對象復制到Survivor區(qū),清空Eden和另一Survivor區(qū),避免內(nèi)存碎片,提升回收速度。老年代:使用標記-清除或標記-整理算法,應對存活率高、內(nèi)存占用大的對象,減少頻繁Full GC的開銷。
- 減少GC停頓時間,新生代GC(Minor GC)頻率高但耗時短,老年代GC(Major/Full GC)頻率低但耗時長。分代后,短命對象回收不會頻繁觸發(fā)老年代GC,整體系統(tǒng)吞吐量更高。
- 內(nèi)存管理靈活性,不同代可獨立配置GC參數(shù)(如Survivor區(qū)大小、晉升閾值),適配不同業(yè)務場景。例如,通過調(diào)整對象晉升年齡(默認15次Minor GC后晉升)控制老年代壓力。
總結:分代策略通過對象生命周期分類、算法優(yōu)化和資源隔離,實現(xiàn)高效GC與低停頓,是JVM性能優(yōu)化的核心設計之一。
7、常見的垃圾回收器有哪些?

如上圖,重點關注 CMS+parNewGC組合、Parallel Scavenge+Parallel Old組合、G1GC
- ParNew 收集器
- 定位:新生代收集器,多線程并行回收,與 CMS 搭配使用。
- 回收過程:
Minor GC(觸發(fā)條件:Eden 區(qū)滿),STW:暫停所有用戶線程。
復制算法:將 Eden 區(qū)和其中一個 Survivor 區(qū)的存活對象復制到另一個空閑 Survivor 區(qū),清空原區(qū)域。
對象晉升:若對象年齡超過閾值(默認 15 次 Minor GC),或 Survivor 區(qū)空間不足,則晉升到老年代。 - 特點:并行多線程回收,吞吐量高,但需短暫 STW。與 CMS 搭配時,需注意內(nèi)存碎片問題。
- CMS(Concurrent Mark Sweep)收集器
- 定位:老年代收集器,追求低停頓,已逐漸被 G1 取代。
- 回收過程(4 階段):
初始標記(Initial Mark),STW:標記 GC Roots 直接關聯(lián)的對象,耗時極短。
并發(fā)標記(Concurrent Marking),并發(fā)執(zhí)行:從 GC Roots 遍歷整個對象圖,標記所有存活對象。
重新標記(Remark),STW:修正并發(fā)標記期間因用戶線程運行產(chǎn)生的對象變動(如新增引用)。
并發(fā)清除(Concurrent Sweep),并發(fā)執(zhí)行:清理標記為垃圾的對象,釋放內(nèi)存(標記-清除算法)。 -
關鍵問題:
浮動垃圾:并發(fā)階段產(chǎn)生的新垃圾需下次 GC 處理。
碎片化:標記-清除導致內(nèi)存碎片,可能觸發(fā) Full GC。
并發(fā)失?。簝?nèi)存不足時切換為 Serial Old 收集器。
image.png
- G1(Garbage-First)收集器
- 定位:全堆收集器,兼顧低延遲與高吞吐量,JDK 9+ 默認。
- 回收過程(4 階段):
初始標記(Initial Mark),STW:標記 GC Roots 直接關聯(lián)的對象,并同步記錄 TAMS(Top of the Mark Stack)指針。
并發(fā)標記(Concurrent Marking),并發(fā)執(zhí)行:遍歷堆內(nèi)所有對象,標記存活對象,記錄 SATB(Snapshot-At-The-Beginning)快照。
最終標記(Final Remark),STW:修正并發(fā)階段因用戶線程修改引用關系導致的 SATB 快照差異。
篩選回收(Mixed Collection),STW:根據(jù)回收收益(垃圾量/回收時間)選擇多個 Region 組成回收集,將存活對象復制到新 Region,清理舊 Region(標記-整理算法)。 - 核心機制:
Region 劃分:堆內(nèi)存劃分為多個等大小的獨立區(qū)域,動態(tài)扮演新生代或老年代。
混合回收:觸發(fā)條件為老年代占用率超過閾值(默認 45%),逐步回收高垃圾率的 Region。
可預測停頓:通過參數(shù) -XX:MaxGCPauseMillis 控制最大停頓時間。 -
大對象處理:Humongous 區(qū)域:超過 Region 50% 的對象存入 Humongous 區(qū),回收時與其他 Region 同步處理
image.png
8、如何解決GC 三色標記法漏標問題?
三色標記法含義:
- 白色(White):未被掃描的對象,默認初始狀態(tài),若最終仍為白色則判定為垃圾。
- 灰色(Gray):已掃描但存在未處理的引用,需進一步遍歷其子對象。
- 黑色(Black):已掃描且所有子對象均處理完畢,確認存活。
CMS:增量更新(Incremental Update)
- 原理:當黑色對象新增對白色對象的引用時,通過寫屏障將黑色對象重新標記為灰色,觸發(fā)重新標記階段(STW)時,以該灰色對象為根重新掃描其引用鏈,確保白色對象被標記為存活。
- 關鍵點:關注引用的增加(A→C)。需重新掃描黑色對象的完整引用鏈,導致 STW 時間較長。
- 缺點:效率較低(需遍歷新增引用的完整鏈路)。
G1:SATB(Snapshot-At-The-Beginning)
- 原理:并發(fā)標記開始時記錄對象圖的原始快照。當灰色對象斷開對白色對象的引用時,通過寫屏障將舊引用推入 SATB 隊列,在最終標記階段以白色對象為根重新掃描,確保其存活。
- 關鍵點:關注引用的刪除(B→C 的引用消失)。僅處理并發(fā)階段刪除的引用,STW 時間更短。
-
缺點:可能產(chǎn)生浮動垃圾(白色對象本應被回收但存活到下次 GC)。
image.png
9、如何解決GC跨代引用問題?
- 跨代引用問題的核心
- 場景:YGC 時,新生代對象可能被老年代對象引用,需遍歷老年代判斷存活性,但老年代引用關系復雜,全量掃描耗時高。
- 關鍵矛盾:跨代引用極少,但頻繁 YGC 時全局掃描老年代效率低下。
- CMS 解決方案
- 卡表(Card Table):
作用:記錄老年代內(nèi)存塊(卡頁,512 字節(jié))是否包含對新生代的引用。
實現(xiàn):通過寫屏障(內(nèi)存屏障)在對象賦值時標記卡頁為“臟卡”,YGC 時僅掃描臟卡對應的內(nèi)存區(qū)域。 - 增量更新(Incremental Update):
原理:當黑色對象新增對白色對象的引用時,將黑色對象標記為灰色,最終標記階段重新掃描其引用鏈,避免漏標。
缺點:需遍歷黑色對象的完整引用鏈,STW 時間較長。
- G1 解決方案
- 卡表 + RSet(Remembered Set):
卡表:與 CMS 類似,記錄跨代引用的內(nèi)存塊。
RSet:每個 Region 維護一個哈希表,記錄其他 Region 對本 Region 的引用,YGC 時僅掃描相關 Region。 - SATB(Snapshot-At-The-Beginning):
原理:并發(fā)標記開始時記錄對象圖快照。若灰色對象斷開對白色對象的引用,通過 SATB 隊列在最終標記階段重新掃描白色對象,避免漏標。
浮動垃圾:白色對象可能被誤判為存活(本應回收),但減少 STW 時間。

8、垃圾回收器的常見參數(shù)有哪些?
- Java棧:
-Xss #設置線程最大??臻g
- Java 堆:
-Xms #設置堆空間初始大?。ㄐ律?老年代)
-Xmx #設置堆空間最大大?。ㄐ律?老年代)
-Xmn #新生代大小
-XX:NewSize=256M #新生代最小 256m內(nèi)存
-XX:MaxNewSize=1024M #新生代最大 1024m內(nèi)存
-XX:NewRatio=<int> #設置老年代與新生代內(nèi)存的比值
-XX:SurvivorRatio=<int> #設置Eden和 S0和 S1大小比例。設置成 8 代表Eden:S0:S1 = 8:1:1
-XX:MetaspaceSize=N #設置 Metaspace 的初始大小,觸及MetaspaceSize,觸發(fā)Full GC
-XX:MaxMetaspaceSize=N #設置 Metaspace 的最大大小,默認為-1,當沒有本地內(nèi)存可用,直接 OOM
-XX:MaxTenuringThreshold #設置默認的晉升年齡
- GC 相關
-XX:+UseSerialGC #串行垃圾收集器
-XX:+UseParallelGC #并行垃圾收集器
-XX:+UseConcMarkSweepGC #CMS 垃圾收集器
-XX:+UseG1GC #G1 垃圾收集器
- GC 日志相關
# 必選
# 打印基本 GC 信息
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
# 打印對象分布
-XX:+PrintTenuringDistribution
# 打印堆數(shù)據(jù)
-XX:+PrintHeapAtGC
# 打印Reference處理信息
# 強引用/弱引用/軟引用/虛引用/finalize 相關的方法
-XX:+PrintReferenceGC
# 打印STW時間
-XX:+PrintGCApplicationStoppedTime
# 可選
# 打印safepoint信息,進入 STW 階段之前,需要要找到一個合適的 safepoint
-XX:+PrintSafepointStatistics
-XX:PrintSafepointStatisticsCount=1
# GC日志輸出的文件路徑
-Xloggc:/path/to/gc-%t.log
# 開啟日志文件分割
-XX:+UseGCLogFileRotation
# 最多分割幾個文件,超過之后從頭文件開始寫
-XX:NumberOfGCLogFiles=14
# 每個文件上限大小,超過就觸發(fā)分割
-XX:GCLogFileSize=50M
- 處理 OOM
-XX:+HeapDumpOnOutOfMemoryError #示 JVM 在遇到 OutOfMemoryError 錯誤時將 heap 轉儲到物理文件中
-XX:HeapDumpPath=./java_pid<pid>.hprof #表示要寫入文件的路徑; 可以給出任何文件名; 但是,如果 JVM 在名稱中找到一個 <pid> 標記,則當前進程的進程 id 將附加到文件名中,并使用.hprof格式
-XX:OnOutOfMemoryError="< cmd args >;< cmd args >" #用于發(fā)出緊急命令,以便在內(nèi)存不足的情況下執(zhí)行; 應該在 cmd args 空間中使用適當?shù)拿?。例如,如果我們想在?nèi)存不足時重啟服務器,我們可以設置參數(shù): -XX:OnOutOfMemoryError="shutdown -r"
-XX:+UseGCOverheadLimit #它限制在拋出 OutOfMemory 錯誤之前在 GC 中花費的 VM 時間的比例
- 配置示例G1
JVM_EXT_ARGS
-Dspring.profiles.active=prod -Dorg.jboss.logging.provider=slf4j
JVM_GC
-XX:-DisableExplicitGC -XX:+UseG1GC -XX:MaxGCPauseMillis=300 -XX:G1ReservePercent=15 -XX:+PrintFlagsFinal -XX:+PrintFlagsFinal -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+PrintReferenceGC -verbose:gc -Xloggc:logs/loggc_%p.log
JVM_HEAP
-XX:+AggressiveOpts -Xmx4096m -Xms4096m -Xss512k -XX:MaxDirectMemorySize=512M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M -XX:AutoBoxCacheMax=20000 -XX:+UseStringDeduplication -XX:InitiatingHeapOccupancyPercent=60 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:-UseLargePages -XX:-UseBiasedLocking -XX:+HeapDumpOnOutOfMemoryError -XX:+ParallelRefProcEnabled
TEST_URL
http://localhost:8080/monitor/alive
/usr/local/java8/bin/java -server -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.io.tmpdir=/tmp -Djava.net.preferIPv6Addresses=false -Dspring.profiles.active=prod -Dorg.jboss.logging.provider=slf4j
-XX:ActiveProcessorCount=4 -XX:-UseContainerSupport -XX:+AggressiveOpts -Xmx4096m -Xms4096m -Xss512k -XX:MaxDirectMemorySize=512M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M -XX:AutoBoxCacheMax=20000 -XX:+UseStringDeduplication -XX:InitiatingHeapOccupancyPercent=60 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:-OmitStackTraceInFastThrow -XX:+AlwaysPreTouch -XX:-UseLargePages -XX:-UseBiasedLocking -XX:+HeapDumpOnOutOfMemoryError -XX:+ParallelRefProcEnabled -XX:-DisableExplicitGC -XX:+UseG1GC -XX:MaxGCPauseMillis=300 -XX:G1ReservePercent=15 -XX:+PrintFlagsFinal -XX:+PrintFlagsFinal -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+PrintReferenceGC
-verbose:gc -Xloggc:logs/loggc_%p.log -XX:ErrorFile=/opt/logs/{appkey}/vmerr.log -XX:HeapDumpPath=/opt/logs/{appkey}/HeapDump -jar ./{appkey}.jar
Non-default VM flags: -XX:ActiveProcessorCount=4 -XX:+AggressiveOpts -XX:+AlwaysPreTouch -XX:AutoBoxCacheMax=20000 -XX:CICompilerCount=3 -XX:CompressedClassSpaceSize=528482304 -XX:ConcGCThreads=1 -XX:-DisableExplicitGC -XX:ErrorFile=null -XX:G1HeapRegionSize=2097152 -XX:G1ReservePercent=15 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=4294967296 -XX:InitiatingHeapOccupancyPercent=60 -XX:MarkStackSize=4194304 -XX:MaxDirectMemorySize=536870912 -XX:MaxGCPauseMillis=300 -XX:MaxHeapSize=4294967296 -XX:MaxMetaspaceSize=536870912 -XX:MaxNewSize=2575302656 -XX:MetaspaceSize=268435456 -XX:MinHeapDeltaBytes=2097152 -XX:-OmitStackTraceInFastThrow -XX:+ParallelRefProcEnabled -XX:+PrintAdaptiveSizePolicy -XX:+PrintFlagsFinal -XX:+PrintGC -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintReferenceGC -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 -XX:+PrintTenuringDistribution -XX:SoftRefLRUPolicyMSPerMB=0 -XX:ThreadStackSize=512 -XX:-UseBiasedLocking -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseContainerSupport -XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC -XX:-UseLargePages -XX:+UseStringDeduplication
- 配置示例CMS
JVM_EXT_ARGS
-Dspring.profiles.active=prod -Dorg.jboss.logging.provider=slf4j
JVM_GC
-XX:+PrintFlagsFinal -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:logs/gc.log -XX:+UseConcMarkSweepGC
JVM_HEAP
-Xmx4608m -Xms4608m -Xmn2560m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError
TEST_URL
http://localhost:8080/monitor/alive
/usr/local/java8/bin/java -server -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.io.tmpdir=/tmp -Djava.net.preferIPv6Addresses=false -Dspring.profiles.active=prod -Dorg.jboss.logging.provider=slf4j
-XX:ActiveProcessorCount=4 -XX:-UseContainerSupport -Xmx4608m -Xms4608m -Xmn2560m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintFlagsFinal -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:logs/gc.log -XX:+UseConcMarkSweepGC
-XX:ErrorFile=/opt/logs/{appkey}/vmerr.log -XX:HeapDumpPath=/opt/logs/{appkey}/HeapDump -jar ./{appkey}.jar
Non-default VM flags: -XX:ActiveProcessorCount=4 -XX:CICompilerCount=3 -XX:CompressedClassSpaceSize=528482304 -XX:ErrorFile=null -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=4831838208 -XX:MaxHeapSize=4831838208 -XX:MaxMetaspaceSize=536870912 -XX:MaxNewSize=2684354560 -XX:MaxTenuringThreshold=6 -XX:MetaspaceSize=536870912 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=2684354560 -XX:OldPLABSize=16 -XX:OldSize=2147483648 -XX:+PrintFlagsFinal -XX:+PrintGC -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseContainerSupport -XX:+UseFastUnorderedTimeStamps -XX:+UseParNewGC


9、Minor Gc 和 Full GC 有什么不同呢

CMS 收集器中的表現(xiàn)
- Minor GC
觸發(fā)條件:Eden 區(qū)滿時觸發(fā),使用復制算法將存活對象移動到 Survivor 區(qū)。
關鍵機制:對象年齡達到閾值(默認 15 次 Minor GC 后)晉升到老年代。動態(tài)年齡判定:若 Survivor 區(qū)剩余空間不足以容納存活對象,則直接晉升。 - Full GC
觸發(fā)場景:Promotion Failure:Survivor 區(qū)溢出且老年代空間不足。CMS Concurrency Mode Failure:并發(fā)標記階段用戶線程產(chǎn)生過多垃圾,老年代預留空間耗盡。
回收策略:停止所有用戶線程,對整個堆進行標記-清除或標記-整理
G1 收集器中的表現(xiàn)
- Mixed GC(類比 Full GC 的部分行為)
觸發(fā)條件:老年代占用超過 45% 時觸發(fā),回收部分老年代 Region。
特點:分階段回收:優(yōu)先回收垃圾比例高的 Region(Garbage First)。停頓可控:通過 MaxGCPauseMillis 參數(shù)限制單次停頓時間。 - Full GC(G1 的特殊場景)
觸發(fā)場景:元空間(Metaspace)不足?;旌匣厥諢o法滿足停頓目標時退化為 Full GC。
回收策略:對整個堆進行全量標記-整理,耗時顯著增加。


