在閱讀《深入理解Java虛擬機(jī)》及相關(guān)技術(shù)資料(如51CTO的解讀)后,我們對(duì)Java虛擬機(jī)(JVM)的核心架構(gòu),尤其是其內(nèi)存模型,有了更深刻的認(rèn)識(shí)。JVM內(nèi)存模型不僅是程序運(yùn)行的基石,更是其高效數(shù)據(jù)處理和存儲(chǔ)支持服務(wù)的核心體現(xiàn)。本文將對(duì)此進(jìn)行和梳理。
一、JVM內(nèi)存模型:程序運(yùn)行的舞臺(tái)
JVM內(nèi)存模型定義了Java程序在運(yùn)行期間如何使用內(nèi)存。它不僅是《Java虛擬機(jī)規(guī)范》中規(guī)定的重要組成部分,也是理解JVM性能調(diào)優(yōu)的關(guān)鍵。其主要分為以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū):
- 程序計(jì)數(shù)器(Program Counter Register):
- 角色:當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
- 特性:線程私有,生命周期與線程相同。它是唯一一個(gè)在《Java虛擬機(jī)規(guī)范》中沒(méi)有規(guī)定任何
OutOfMemoryError情況的區(qū)域。
- Java虛擬機(jī)棧(Java Virtual Machine Stacks):
- 角色:描述Java方法執(zhí)行的內(nèi)存模型。每個(gè)方法在執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。
- 特性:線程私有。局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型、對(duì)象引用和
returnAddress類型。該區(qū)域可能拋出StackOverflowError和OutOfMemoryError。
- 本地方法棧(Native Method Stack):
- 角色:為JVM使用到的Native(本地)方法服務(wù)。
- 特性:與虛擬機(jī)棧類似,也可能拋出
StackOverflowError和OutOfMemoryError。
- Java堆(Java Heap):
- 角色:JVM內(nèi)存中最大的一塊,被所有線程共享。幾乎所有的對(duì)象實(shí)例以及數(shù)組都在這里分配內(nèi)存。
- 特性:是垃圾收集器管理的主要區(qū)域,因此常被稱為“GC堆”。從內(nèi)存回收角度看,現(xiàn)代收集器基本采用分代收集算法,故堆可細(xì)分為:新生代(Eden區(qū)、From Survivor區(qū)、To Survivor區(qū))和老年代。該區(qū)域是
OutOfMemoryError的“高發(fā)區(qū)”。
- 方法區(qū)(Method Area):
- 角色:存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)。
- 特性:線程共享。在HotSpot虛擬機(jī)中,它常被稱為“永久代”(JDK 8之前)或“元空間”(JDK 8及之后)。該區(qū)域也會(huì)拋出
OutOfMemoryError。
- 運(yùn)行時(shí)常量池(Runtime Constant Pool):
- 角色:方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號(hào)引用。
- 特性:具備動(dòng)態(tài)性,運(yùn)行期間也可以將新的常量放入池中(如
String.intern()方法)。
- 直接內(nèi)存(Direct Memory):
- 角色:并非JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是《Java虛擬機(jī)規(guī)范》定義的內(nèi)存區(qū)域。但通過(guò)NIO的
DirectByteBuffer可以直接在堆外分配內(nèi)存,然后通過(guò)存儲(chǔ)在Java堆里的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。
- 特性:避免了在Java堆和Native堆之間來(lái)回復(fù)制數(shù)據(jù),能顯著提高性能。但分配和回收成本較高,也可能導(dǎo)致
OutOfMemoryError。
二、數(shù)據(jù)處理與存儲(chǔ)支持服務(wù):內(nèi)存模型的效能體現(xiàn)
JVM內(nèi)存模型并非靜態(tài)的存儲(chǔ)劃分,其背后是一整套高效的數(shù)據(jù)處理和存儲(chǔ)支持服務(wù),確保Java應(yīng)用穩(wěn)定、高性能運(yùn)行。
- 對(duì)象創(chuàng)建與內(nèi)存分配:
- 當(dāng)JVM遇到一條
new指令時(shí),首先檢查指令參數(shù)能否在常量池中定位到一個(gè)類的符號(hào)引用,并檢查該類是否已被加載、解析和初始化。在Java堆中為新生對(duì)象分配內(nèi)存。分配方式包括“指針碰撞”和“空閑列表”,選擇哪種取決于堆內(nèi)存是否規(guī)整,而堆內(nèi)存是否規(guī)整又由采用的垃圾收集器是否帶有壓縮整理功能決定。
- 內(nèi)存布局與訪問(wèn)定位:
- 對(duì)象在堆內(nèi)存中的存儲(chǔ)布局可分為三塊:對(duì)象頭(包含哈希碼、GC分代年齡、鎖狀態(tài)標(biāo)志等運(yùn)行時(shí)數(shù)據(jù)以及類型指針)、實(shí)例數(shù)據(jù)和對(duì)齊填充。
- 訪問(wèn)定位:Java程序需要通過(guò)棧上的reference數(shù)據(jù)來(lái)操作堆上的具體對(duì)象。主流的訪問(wèn)方式有使用句柄和直接指針兩種。HotSpot主要使用直接指針?lè)绞?,其?yōu)點(diǎn)是訪問(wèn)速度更快。
- 垃圾收集與內(nèi)存回收:
- 這是JVM最核心的存儲(chǔ)支持服務(wù)。垃圾收集器自動(dòng)管理堆和方法區(qū)的內(nèi)存,通過(guò)可達(dá)性分析算法等判定對(duì)象是否存活,并回收已死亡對(duì)象占用的空間。不同的垃圾收集器(如Serial, Parallel, CMS, G1, ZGC等)及其搭配,提供了在吞吐量、延遲和內(nèi)存占用之間不同側(cè)重點(diǎn)的解決方案。
- 內(nèi)存異常處理:
- JVM提供了完善的錯(cuò)誤檢測(cè)機(jī)制。當(dāng)線程請(qǐng)求的棧深度超過(guò)虛擬機(jī)允許的最大深度時(shí),拋出
StackOverflowError;當(dāng)擴(kuò)展棧時(shí)無(wú)法申請(qǐng)到足夠內(nèi)存時(shí),拋出OutOfMemoryError。Java堆和方法區(qū)無(wú)法滿足內(nèi)存分配需求時(shí),也會(huì)拋出OutOfMemoryError。理解這些異常發(fā)生的場(chǎng)景是進(jìn)行JVM調(diào)優(yōu)和故障診斷的基礎(chǔ)。
- 高效并發(fā)支持:
- JVM內(nèi)存模型與Java內(nèi)存模型(JMM)緊密相關(guān)。JMM通過(guò)定義主內(nèi)存與工作內(nèi)存的交互協(xié)議(如
lock, unlock, read, load, use, assign, store, write),以及volatile、synchronized等關(guān)鍵字,解決了多線程環(huán)境下的可見(jiàn)性、原子性和有序性問(wèn)題,為并發(fā)數(shù)據(jù)處理提供了底層支持。
###
《深入理解Java虛擬機(jī)》為我們揭示,JVM內(nèi)存模型是一個(gè)設(shè)計(jì)精良、分工明確的有機(jī)整體。從線程私有的程序計(jì)數(shù)器、棧,到共享的堆和方法區(qū),每個(gè)區(qū)域各司其職,共同支撐起Java程序的運(yùn)行。而背后強(qiáng)大的垃圾收集、內(nèi)存分配、并發(fā)訪問(wèn)控制等數(shù)據(jù)處理與存儲(chǔ)支持服務(wù),則是JVM能夠?qū)崿F(xiàn)“一次編寫,到處運(yùn)行”的自動(dòng)內(nèi)存管理承諾的關(guān)鍵。深入理解這些原理,是進(jìn)行高性能、高穩(wěn)定性Java應(yīng)用開(kāi)發(fā)和調(diào)優(yōu)的必經(jīng)之路。