
1. JVM內存模型概述對象居住的城市布局當Java程序運行時JVM會為它分配一塊內存區(qū)域這塊內存被劃分為多個功能不同的街區(qū)每個街區(qū)都有特定的用途和管理規(guī)則。理解這個內存布局就像了解一座城市的規(guī)劃——知道超市、住宅區(qū)、政府機構分別在哪里才能高效地生活和工作。JVM內存主要分為以下幾個核心區(qū)域堆Heap這是最大的居民區(qū)幾乎所有對象實例都居住在這里。堆又分為新生代Young Generation和老年代Old Generation新生代進一步包含Eden區(qū)、Survivor區(qū)S0和S1。就像城市有不同檔次的小區(qū)新創(chuàng)建的對象先住在Eden區(qū)的廉租房熬過幾次GC后可能搬到Survivor區(qū)的公寓最終有些對象會晉升到老年代的別墅區(qū)。方法區(qū)Method Area存儲類信息、常量、靜態(tài)變量等相當于城市的市政廳和圖書館。在HotSpot VM中也被稱為永久代PermGen但在Java 8后被元空間Metaspace取代。虛擬機棧VM Stack每個線程私有的工作間存儲局部變量表、操作數(shù)棧、方法出口等信息。方法調用就像在這個工作間里堆疊文件形成棧幀Stack Frame。本地方法棧Native Method Stack為本地Native方法服務類似于外事辦公室。程序計數(shù)器PC Register線程私有的進度條記錄當前線程執(zhí)行的字節(jié)碼指令地址。關鍵理解對象主要居住在堆中但對象引用變量可能分布在?;蚍椒▍^(qū)。就像你住在某小區(qū)堆中的對象實例但你的名片可能放在辦公室抽屜棧中的引用或公司通訊錄方法區(qū)的靜態(tài)變量。2. 對象的一生從誕生到GC回收2.1 對象的創(chuàng)建與內存分配當我們在Java中寫下new Object()時JVM會執(zhí)行以下步驟類加載檢查首先檢查這個對象的類是否已加載。如果沒有先執(zhí)行類加載過程會用到方法區(qū)。內存分配大多數(shù)情況下對象在Eden區(qū)分配內存。JVM使用指針碰撞Bump the Pointer或空閑列表Free List方式分配空間具體取決于堆是否規(guī)整。// 示例創(chuàng)建對象的內存分配過程 Object obj new Object(); // 1. 在Eden區(qū)劃分一塊內存存放Object實例 // 2. 將obj引用存入當前線程的棧幀中的局部變量表內存初始化分配到的內存空間被初始化為零值null, 0, false等。對象頭設置設置對象的Mark Word存儲哈希碼、GC分代年齡等和類型指針指向方法區(qū)中的類元數(shù)據(jù)。執(zhí)行構造方法按照Java代碼初始化對象。內存分配策略細節(jié)TLABThread Local Allocation Buffer每個線程在Eden區(qū)有一小塊私有區(qū)域避免多線程競爭。可通過-XX:/-UseTLAB參數(shù)控制。大對象直接進入老年代避免在Eden區(qū)及Survivor區(qū)之間大量復制。通過-XX:PretenureSizeThreshold設置閾值。2.2 對象的內存布局一個Java對象在堆中的存儲布局可分為三部分對象頭HeaderMark Word存儲對象自身的運行時數(shù)據(jù)哈希碼、GC分代年齡、鎖狀態(tài)標志等類型指針指向方法區(qū)的類元數(shù)據(jù)數(shù)組對象還會有數(shù)組長度數(shù)據(jù)實例數(shù)據(jù)Instance Data對象真正存儲的有效信息即各個字段內容對齊填充Padding非必需部分僅起占位作用HotSpot VM要求對象起始地址必須是8字節(jié)的整數(shù)倍[對象頭][實例數(shù)據(jù)][對齊填充]2.3 對象的訪問定位Java程序需要通過棧上的reference數(shù)據(jù)來操作堆上的具體對象主流的訪問方式有兩種句柄訪問在堆中劃分一塊內存作為句柄池reference存儲的是對象的句柄地址句柄包含對象實例數(shù)據(jù)和類型數(shù)據(jù)的地址信息優(yōu)點reference穩(wěn)定對象移動時只需修改句柄缺點訪問需要兩次指針定位速度略慢直接指針HotSpot采用reference直接存儲對象地址對象內存布局中包含類型指針優(yōu)點訪問速度快節(jié)省一次指針定位缺點對象移動時reference需要修改3. GC與內存管理對象的生存考驗3.1 分代收集理論JVM采用分代收集Generational Collection策略基于兩個經驗法則弱分代假說絕大多數(shù)對象都是朝生夕死的強分代假說熬過越多次GC的對象越難消亡因此堆內存被劃分為新生代和老年代新生代Young GenerationEden區(qū)對象出生地Survivor區(qū)S0和S1存放GC后存活的對象Minor GC/Young GC新生代的垃圾收集對象晉升年齡計數(shù)器達到閾值默認15或Survivor空間不足時晉升老年代老年代Old Generation存放長期存活的對象Major GC/Full GC老年代的垃圾收集通常伴隨新生代收集3.2 常見GC算法標記-清除Mark-Sweep第一階段標記所有需要回收的對象第二階段統(tǒng)一回收被標記對象缺點產生內存碎片標記-復制Mark-Copy將內存分為兩塊每次只使用一塊GC時將存活對象復制到另一塊然后清理當前塊適用于新生代Eden和Survivor區(qū)標記-整理Mark-Compact標記過程與標記-清除相同后續(xù)步驟讓所有存活對象向一端移動適用于老年代3.3 經典垃圾收集器Serial收集器單線程收集器新生代采用標記-復制老年代采用標記-整理適合客戶端模式下的JVMParNew收集器Serial收集器的多線程版本新生代并行收集老年代串行收集Parallel Scavenge收集器新生代收集器關注吞吐量采用標記-復制算法CMSConcurrent Mark Sweep收集器以獲取最短回收停頓時間為目標基于標記-清除算法運作過程復雜包括初始標記、并發(fā)標記、重新標記、并發(fā)清除G1Garbage-First收集器面向服務端應用的收集器將堆劃分為多個Region優(yōu)先回收價值最大的Region整體上看是標記-整理局部看是標記-復制4. 內存問題排查與優(yōu)化實戰(zhàn)4.1 常見內存問題內存泄漏對象不再使用但無法被GC回收常見原因靜態(tài)集合、未關閉的資源、監(jiān)聽器未注銷等內存溢出OOMJava堆溢出OutOfMemoryError: Java heap space方法區(qū)溢出OutOfMemoryError: PermGen space/Metaspace棧溢出StackOverflowErrorGC效率問題Full GC頻繁GC停頓時間過長4.2 排查工具與技巧命令行工具jps查看Java進程jstat監(jiān)控JVM統(tǒng)計信息jmap生成堆轉儲快照jstack生成線程快照可視化工具JConsoleVisualVMEclipse Memory Analyzer Tool (MAT)關鍵參數(shù)監(jiān)控# 查看GC情況 jstat -gcutil pid 1000 10 # 生成堆轉儲文件 jmap -dump:formatb,fileheap.hprof pid4.3 調優(yōu)實戰(zhàn)案例案例1電商系統(tǒng)Full GC頻繁現(xiàn)象系統(tǒng)運行一段時間后Full GC頻繁每次停頓2-3秒排查步驟使用jstat -gcutil發(fā)現(xiàn)老年代很快被填滿用jmap -histo查看大對象發(fā)現(xiàn)大量緩存對象檢查代碼發(fā)現(xiàn)本地緩存沒有大小限制解決方案改用Guava Cache并設置合理的大小和過期策略案例2接口響應時間波動大現(xiàn)象某些時段接口響應時間從50ms突增到2s排查步驟通過GC日志發(fā)現(xiàn)與Young GC時間吻合發(fā)現(xiàn)新生代太小默認參數(shù)下只有幾十MB調整參數(shù)-Xmn256m設置新生代為256MB效果Young GC頻率降低停頓時間從200ms降到50ms4.4 關鍵JVM參數(shù)堆大小設置-Xms初始堆大小-Xmx最大堆大小-Xmn新生代大小GC相關-XX:UseG1GC使用G1收集器-XX:MaxGCPauseMillis200目標最大GC停頓時間-XX:InitiatingHeapOccupancyPercent45觸發(fā)并發(fā)GC周期的堆占用率內存溢出時自動轉儲-XX:HeapDumpOnOutOfMemoryError-XX:HeapDumpPath/path/to/dump.hprof5. 面試高頻問題深度解析5.1 對象創(chuàng)建過程問題描述Java中new Object()的完整執(zhí)行過程深度解析類加載檢查檢查類元信息是否已加載如果沒有則執(zhí)行類加載內存分配根據(jù)內存是否規(guī)整采用指針碰撞或空閑列表方式在Eden區(qū)分配內存內存空間初始化將分配到的內存初始化為零值對象頭設置設置Mark Word哈希碼、GC分代年齡等和類型指針執(zhí)行init方法按照程序員的意圖初始化對象構造方法等5.2 內存分配規(guī)則問題對象一定在堆上分配嗎特殊場景棧上分配通過逃逸分析確定對象不會逃逸出方法可能直接在棧上分配需開啟-XX:DoEscapeAnalysis標量替換將對象拆解為基本類型變量分配在棧上需開啟-XX:EliminateAllocationsTLAB分配線程本地分配緩沖避免多線程競爭Eden區(qū)的指針5.3 GC Roots有哪些完整GC Roots包括虛擬機棧中引用的對象局部變量方法區(qū)中類靜態(tài)屬性引用的對象方法區(qū)中常量引用的對象本地方法棧中JNI引用的對象Java虛擬機內部引用基本類型對應的Class對象等被同步鎖持有的對象反映Java虛擬機內部情況的JMXBean等5.4 四種引用類型強引用普通的Object obj new Object()只要強引用存在對象就不會被回收軟引用SoftReference內存不足時會被回收適合做緩存弱引用WeakReference下次GC時會被回收適合實現(xiàn)規(guī)范映射虛引用PhantomReference無法通過它獲取對象主要用于跟蹤對象被回收的活動5.5 如何判斷對象已死引用計數(shù)法Java未采用給對象添加引用計數(shù)器循環(huán)引用時無法回收可達性分析法Java采用從GC Roots開始向下搜索不可達的對象標記為可回收兩次標記過程第一次標記后執(zhí)行finalize()方法第二次標記后回收6. 性能優(yōu)化實戰(zhàn)技巧6.1 對象分配優(yōu)化減少臨時對象避免在循環(huán)中創(chuàng)建對象重用對象如通過對象池// 反例每次循環(huán)都創(chuàng)建新對象 for(int i0; i10000; i) { String s new String(abc); } // 正例重用對象 String s abc; for(int i0; i10000; i) { // 直接使用s }控制對象大小減少不必要的字段使用基本類型而非包裝類注意對象對齊帶來的空間浪費6.2 集合類優(yōu)化初始化合適容量ArrayList/HashMap等集合指定初始大小避免頻繁擴容// 已知大小約為100時 ListString list new ArrayList(100);謹慎使用大對象集合對于大集合考慮使用原始類型集合如Trove或采用分頁加載方式6.3 緩存策略優(yōu)化合理使用軟/弱引用緩存// 使用軟引用實現(xiàn)緩存 SoftReferenceBigObject cache new SoftReference(new BigObject());控制緩存大小使用LRU等淘汰策略設置合理的過期時間6.4 并發(fā)環(huán)境優(yōu)化減少鎖競爭縮小同步代碼塊使用讀寫鎖替代獨占鎖考慮無鎖數(shù)據(jù)結構對象池技術對于創(chuàng)建成本高的對象如數(shù)據(jù)庫連接使用成熟的池化框架如HikariCP7. 新一代垃圾收集器特性7.1 ZGCZ Garbage Collector特點低延遲目標10ms可擴展支持TB級堆并發(fā)標記-整理算法基于Region的內存布局使用染色指針技術啟用參數(shù)-XX:UseZGC7.2 Shenandoah特點低停頓時間并發(fā)壓縮與應用程序線程并發(fā)執(zhí)行大多數(shù)GC工作不需要分代收集啟用參數(shù)-XX:UseShenandoahGC7.3 選擇建議追求吞吐量Parallel Scavenge Parallel Old追求低延遲中小堆100GBG1大堆ZGC或Shenandoah云原生環(huán)境考慮ZGC對容器支持更好8. 容器環(huán)境下的JVM內存管理8.1 容器感知問題傳統(tǒng)問題JVM默認讀取物理機內存信息在容器中可能導致內存超出限制被OOM Killer終止解決方案JDK 8u191支持容器感知或顯式設置內存參數(shù)-XX:UseCGroupMemoryLimitForHeap -XX:MaxRAMPercentage75.08.2 最佳實踐不要依賴Xmx/Xms使用百分比參數(shù)-XX:MaxRAMPercentage80.0 -XX:InitialRAMPercentage50.0預留內存為系統(tǒng)、其他進程預留足夠內存通常JVM堆不超過容器內存的75%監(jiān)控與調整使用容器監(jiān)控工具如cAdvisor根據(jù)實際使用情況調整參數(shù)9. 內存模型與多線程9.1 Java內存模型JMM核心概念主內存與工作內存所有變量存儲在主內存每個線程有自己的工作內存緩存內存間交互操作lock/unlockread/loaduse/assignstore/writehappens-before原則程序順序規(guī)則鎖規(guī)則volatile規(guī)則傳遞性等9.2 volatile關鍵字作用保證可見性寫操作立即刷新到主內存禁止指令重排序實現(xiàn)原理通過內存屏障Memory Barrier實現(xiàn)寫操作前后加StoreStore和StoreLoad屏障讀操作前后加LoadLoad和LoadStore屏障9.3 對象頭與鎖對象頭中的Mark Word與鎖狀態(tài)鎖狀態(tài)存儲內容無鎖哈希碼、分代年齡等偏向鎖線程ID、Epoch、分代年齡輕量級鎖指向棧中鎖記錄的指針重量級鎖指向互斥量monitor的指針GC標記空用于GC過程鎖升級過程 無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖10. 未來發(fā)展趨勢值類型Value Types減少對象開銷類似基本類型的復合類型Project Loom輕量級線程虛擬線程可能影響對象分配模式GC技術演進更智能的預測式GC機器學習輔助的GC調優(yōu)內存架構變化非易失性內存支持異構內存管理在實際開發(fā)中理解JVM內存模型和對象分配機制能幫助我們編寫更高效的代碼更準確地診斷內存問題以及更合理地進行JVM調優(yōu)。建議結合具體應用場景通過監(jiān)控工具觀察內存使用情況不斷調整和優(yōu)化參數(shù)配置。