Java程序中經(jīng)常用到的內(nèi)存模塊到底有哪些?
前言
我們寫代碼時(shí),通常會(huì)關(guān)注代碼與對(duì)象之間的流轉(zhuǎn)。但實(shí)際上,我們有沒有認(rèn)真去關(guān)注過 java 程序運(yùn)行時(shí),類、對(duì)象、局部變量與方法調(diào)用鏈?zhǔn)谴娣旁谀睦锏哪?
JVM 內(nèi)存
眾所周知,java 程序是執(zhí)行在 jvm 虛擬機(jī)中的,那在眾多的 jvm 虛擬機(jī)中,HotSpot VM 是當(dāng)下最熱門的或者說使用得最多的 jvm 版本。在 HotSpot VM 中,整個(gè)虛擬機(jī)內(nèi)存被劃分為幾大模塊:
- 堆(Heap)
- 方法區(qū)(Method Area)
- 程序計(jì)數(shù)器(Program Counter Register)
- 虛擬機(jī)棧(JVM Stacks)
- 本地方法棧(Native Method Stacks)
堆(Heap)
該區(qū)域就是我們平時(shí)正常 new 出來的對(duì)象待的地方,該區(qū)域也是我們平時(shí)接觸得最多的區(qū)域,這里細(xì)分為:Young Generation(新生代) 和 Old Generation(老生代) ,也就是我們平時(shí)經(jīng)常關(guān)注的 YGC 和 FGC 的地方,而 Young Generation 中間又再細(xì)分為三個(gè)區(qū)域:Eden、From Survivor、To Survivor,YGC 會(huì)有各種的算法來對(duì)這三塊區(qū)域做針對(duì)性的垃圾回收算法,這里就先不展開討論,有興趣的可以參考 面試官,不要再問我“JAVA GC垃圾回收機(jī)制”了 。
該區(qū)域是所有線程所共享的,而存放的又是主要的業(yè)務(wù)對(duì)象,所以空間相對(duì)來說會(huì)是比較大的。一般可以使用 -Xms設(shè)置堆的最小空間大小 和 -Xmx設(shè)置堆的最大空間大小。
方法區(qū)(Method Area)
方法區(qū)和剛說的棧區(qū)域有很多相似的地方:線程共享、內(nèi)存不連續(xù)、可擴(kuò)展、可垃圾回收,同樣當(dāng)無法再擴(kuò)展時(shí)會(huì)拋出OutOfMemoryError異常。而方法區(qū)通常也被稱為非堆區(qū)域(non-heap),注意這要與堆外內(nèi)存區(qū)分開!
方法區(qū)它存儲(chǔ)的是已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。就是一些與運(yùn)行時(shí)生成的對(duì)象區(qū)別開,是一個(gè)固定存放物資的區(qū)域。
方法區(qū)的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載,一般來說這個(gè)區(qū)域的回收“成績(jī)”比較難以令人滿意,尤其是類型的卸載,條件相當(dāng)苛刻,但是回收確實(shí)是有必要的。
程序計(jì)數(shù)器(Program Counter Register)
程序計(jì)數(shù)器的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,字節(jié)碼解釋器工作時(shí)就是通過改變計(jì)數(shù)器的值來選取下一條字節(jié)碼指令。其中,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴計(jì)數(shù)器來完成。
Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對(duì)于多核處理器來說是一個(gè)內(nèi)核)只會(huì)執(zhí)行一條線程中的指令,必須要有一個(gè)地方儲(chǔ)存每個(gè)線程具體處理到哪一步了,該區(qū)域就是做這件事了。
虛擬機(jī)棧(JVM Stacks)
學(xué)習(xí)過匯編語言的同學(xué)也許會(huì)比較容易理解它的作用。它是一個(gè)記錄線程調(diào)用方法模型的棧,每一條線程私有一個(gè)虛擬機(jī)棧,所以它也與對(duì)應(yīng)線程生死與共。
其每個(gè)方法模型被稱為棧幀,棧幀會(huì)存放 4 個(gè)屬性:局部變量表、操作棧、動(dòng)態(tài)鏈接、方法返回地址。
ps:對(duì)于其中的局部變量,如果是變量為基礎(chǔ)類型,棧幀會(huì)直接存儲(chǔ)對(duì)應(yīng)的值,但如果是高級(jí)類型的話只會(huì)存放值的引用。
本地方法棧(Native Method Stacks)
與虛擬機(jī)棧作用相似,也會(huì)拋出 StackOverflowError 和 OutOfMemoryError 異常。
區(qū)別在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(字節(jié)碼)服務(wù),而本地方法棧是為虛擬機(jī)使用到的Native方法服務(wù)。
對(duì)于 jvm 的內(nèi)存,我們可以用這樣的一張圖來歸納起來以幫助對(duì)比理解。
堆外內(nèi)存(Off-Heap Memory)
區(qū)別于 JVM 內(nèi)存,java 常用的還有堆外內(nèi)存。雖然 JVM 內(nèi)存擁有非常完善的垃圾管理機(jī)制,可以讓開發(fā)人員無需關(guān)注內(nèi)存資源回收隨心所欲地進(jìn)行開發(fā),但也正因?yàn)榇嬖谝贿B串非常復(fù)雜精妙的垃圾管理算法,導(dǎo)致在高并發(fā)情況(特別是寫內(nèi)存多)下可能會(huì)出現(xiàn)頻繁的 YGC 和 FGC (每次 GC 都會(huì)引起程序卡頓),反而會(huì)成為性能瓶頸的幫兇!
針對(duì)這一類的業(yè)務(wù)情況,我們通常會(huì)使用堆外內(nèi)存來提升我們的性能水平。堆外內(nèi)存其實(shí)就是游離于 JVM 管理之外的物理機(jī)內(nèi)存。不屬于 JVM 的管理,自然就沒有了 JVM 的那套 GC 算法,這樣能使我們有更加好的擴(kuò)展性和 IO 速度。
在JAVA中,可以通過Unsafe和NIO包下的ByteBuffer來操作堆外內(nèi)存,也可以使用第三方堆外緩存管理包例如 ohc(off-heap-cache) 來操作。

























