JVM 有兩種類型的線程:守護(hù)線程肝箱,非守護(hù)線程稀蟋,只有所有非守護(hù)線程都結(jié)束之后退客,JVM才會結(jié)束運(yùn)行,退出档玻。守護(hù)線程如GC茫藏,非守護(hù)線程如main务傲。
操作系統(tǒng)內(nèi)存與JVM內(nèi)存的聯(lián)系與區(qū)別
- 操作系統(tǒng)分為棧和堆,棧由操作系統(tǒng)管理看杭,會有操作系統(tǒng)進(jìn)行自動回收楼雹,堆由用戶進(jìn)行分配使用
- JVM內(nèi)存使用的操作系統(tǒng)的堆尖阔,以防JVM分配的內(nèi)存被操作系統(tǒng)回收
- JVM本地方法棧指的是操作系統(tǒng)的棧
- 操作系統(tǒng)的PC寄存器介却,是計算機(jī)上的存儲硬件,與內(nèi)存條一樣的硬件憔鬼,但是寄存區(qū)位于CPU內(nèi)胃夏,被稱為Cache仰禀,用于加快數(shù)據(jù)訪問速度;內(nèi)存是外掛在CPU的數(shù)據(jù)總線上的
- JVM PC寄存器位于操作系統(tǒng)的堆中
JVM規(guī)范中的內(nèi)存空間
1- JVM PC寄存器
PC寄存器配合字節(jié)碼解釋器萍诱,選取下一條字節(jié)碼指令來解釋執(zhí)行污呼,線程私有燕酷,每個線程都有一個PC寄存器。為了確保切換線程后能恢復(fù)到原來進(jìn)程正確的執(zhí)行位置饵蒂。如果執(zhí)行的不是Java方法退盯,而是本地方法Native Method泻肯,這個計數(shù)器值為空(Undefined)软免,如果是Java方法則保存的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址焚挠,可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器蝌衔,會自增取下一條字節(jié)碼指令的地址。這是JVM 規(guī)范的唯一沒有OutMemoryError的內(nèi)存區(qū)域曹锨。
2- Java 虛擬機(jī)棧
- 當(dāng)啟動一個線程時沛简,JVM就會給這個線程分配一個棧斥废,所以棧的生命周期是和線程一樣的牡肉。
- Java方法執(zhí)行的內(nèi)存模型:每個Java方法的執(zhí)行都會創(chuàng)建一個棧幀(Stack Frame),方法的調(diào)用到執(zhí)行完畢(正常退出毛俏,或者異常退出)對應(yīng)著一個棧幀的入棧和出棧的過程
- 一個棧幀包括:局部變量表煌寇、操作數(shù)棧、動態(tài)鏈接擦盾、方法出口等迹卢。
棧幀結(jié)構(gòu):
- 局部變量表:存放的是編譯期間可知的基本數(shù)據(jù)類型徒仓,和對象的引用掉弛,比如方法的參數(shù)變量,還有方法內(nèi)的局部變量谋作;需要注意的是如果這個方法是普通的方法遵蚜,那么還會有自己的本身對象的一個引用(this奈惑,索引為0)肴甸,如果是靜態(tài)方法就沒有這個自身對象的引用。一個單位局部變量空間為32位友扰,64位長度的long和double數(shù)據(jù)類型會占用兩個局部變量空間焕檬。變量通過聲明的順序的索引來進(jìn)行訪問的澳泵,
- 操作數(shù)棧(Operand Stack):是一個存儲中間變量結(jié)果的棧結(jié)構(gòu),只能通過入棧和出棧來進(jìn)行訪問击喂。Java虛擬機(jī)指令是主要是通過操作數(shù)棧來獲取操作數(shù)(Operand)的碰辅,而不是寄存器懂昂。
int a = 100;
int b = 98;
// iload_0,iload_1,iadd,istore_2
//iload指令是指將局部變量表中一個int變量加載到操作棧中
//iadd指令彈出操作數(shù)棧的兩個變量,進(jìn)行加法運(yùn)算没宾,然后將結(jié)果壓入棧中
//istore指令是指將操作棧一個數(shù)值存儲到局部變量表中
int c = a+b;
操作數(shù)棧
- 動態(tài)鏈接(待續(xù))
- 方法出口(待續(xù))
- 通常程序員所說的棧指的就是虛擬機(jī)的局部變量表凌彬。
- 兩種異常:StackOverflowError(棧空間溢出)循衰,當(dāng)線程請求的棧深度大于虛擬機(jī)所允許的深度铲敛,將會拋出此異常;OutMemoryError(椈岫郏空間拓展內(nèi)存溢出)伐蒋,當(dāng)虛擬機(jī)棧支持動態(tài)拓展時迁酸,如果在擴(kuò)展時無法申請到足夠的內(nèi)存時先鱼,就會拋出此異常。
3- 本地方法棧(Native Method)
- 本地方法棧和虛擬機(jī)棧的作用是一樣的奸鬓,只是服務(wù)的對象不一樣焙畔,虛擬機(jī)方法棧是為虛擬機(jī)執(zhí)行java方法(字節(jié)碼)服務(wù)的,而本地方法棧是為虛擬機(jī)執(zhí)行Native方法服務(wù)的串远。
- Sun HotSpot 直接將本地方法棧和虛擬機(jī)棧 合二為一宏多。
- 兩種異常:和虛擬機(jī)棧一樣
4- Java堆(Java Heap)
- 所有線程共享的內(nèi)存區(qū)域,與虛擬機(jī)同生命周期抑淫。
- 主要任務(wù)是存儲對象實例,基本上所有對象實例和數(shù)組都在Heap上分配空間姥闪,但是也不這么絕對始苇,因為編譯器優(yōu)化。
- Java堆是一塊很大的內(nèi)存區(qū)域筐喳,為了加速GC回收的效率催式,把這個堆有按照不同粒度進(jìn)行細(xì)分
- 按代劃分 新生代,老年代避归;再細(xì)分為:Eden空間荣月、From Survivor空間、To Survivor 空間梳毙。
- 從內(nèi)存分配的角度: TLAB(Thread Local Allocation Buffer):線程共享Heap中可以多個線程私有的分配緩存哺窄。
- GC的主要回收區(qū)域
- 物理存儲上不一定連續(xù),只要邏輯上連續(xù)即可;堆空間可以拓展(通過-Xmx萌业,-Xms來控制)坷襟。
- 異常:OutMemoryError 當(dāng)堆中沒有可用內(nèi)存來存儲對象實例
5- 方法區(qū)(Method Area)
所有線程共享的內(nèi)存區(qū)域,與虛擬機(jī)同生命周期生年。
存儲已被虛擬機(jī)加載的類信息婴程、常量、靜態(tài)變量抱婉、編譯后的代碼等
按照代劃分档叔,方法區(qū)在HotSpot中被劃分為永久代(Permanent Generation)
不需要物理連續(xù)的存儲空間,可拓展(通過 -XX:MaxPermSize蒸绩,-XX:MinPermSize)
永久代并不永久衙四,GC會對常量池的回收,以及類型的卸載侵贵。
異常:OutMemoryError届搁,當(dāng)方法區(qū)無法滿足內(nèi)存分配時。
-
運(yùn)行時常量池(Runtime Constant Pool)
- 是方法區(qū)的一部分窍育,Class文件中除了有版本卡睦、字段、方法漱抓、接口等描述信息表锻,還會有一項信息是常量池,用于存放編譯期間生成的各種字面量和符號引用以及翻譯后的直接引用乞娄,這部分內(nèi)容將在類加載后存放在方法區(qū)的運(yùn)行時常量池中瞬逊。
- 運(yùn)行時常量池中的常量,不一定來源于一開始加載的Class文件(編譯期間產(chǎn)生常量)仪或,也可以在運(yùn)行時將新的常量放入常量池中确镊,這是運(yùn)行時常量池動態(tài)性的體現(xiàn)。
JVM規(guī)范外的內(nèi)存空間 -- 直接內(nèi)存(Direct Memory)
- 不是虛擬機(jī)內(nèi)存模型以及數(shù)據(jù)區(qū)的一部分范删,但是頻繁使用蕾域,特別是NIO中,基于通道(channel)與緩沖區(qū)(Buffer)的I/O方式
- 通過Native函數(shù)庫直接分配堆外內(nèi)存到旦,能在一些場景下顯著提高性能旨巷,因為避免了在Java堆和Native堆中來回復(fù)制數(shù)據(jù)
- 不會受到Java堆的大小影響,主要取決于本機(jī)的內(nèi)存添忘,以及處理器的尋址空間采呐。
- OutMemoryError:虛擬機(jī)參數(shù)設(shè)置時,將虛擬機(jī)內(nèi)存設(shè)置超出物理內(nèi)存或者操作系統(tǒng)的限制時搁骑,導(dǎo)致動態(tài)拓展時導(dǎo)致內(nèi)存溢出斧吐。
java7內(nèi)存布局的變化
從JDK7開始永久代的移除工作又固,但永久代仍然存在于JDK7,并沒有完全的移除会通。
- 符號引用(Symbols)轉(zhuǎn)移到了native heap(java堆外);
- 字面量(interned strings)和類的靜態(tài)變量(class statics)轉(zhuǎn)移到了java heap口予。
- Class元數(shù)據(jù)還在方法區(qū)上
java8 元空間
元空間的本質(zhì)和永久代類似,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)涕侈,用于存放Class元數(shù)據(jù)沪停。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存裳涛,這部分內(nèi)存區(qū)域間接被GC管理木张。因此,默認(rèn)情況下端三,元空間的大小僅受本地內(nèi)存限制舷礼,但是實際使用上如果不設(shè)置大小,可能耗盡系統(tǒng)內(nèi)存郊闯。
參考鏈接:
TLAB與PLAB
http://www.reibang.com/p/2343f2c0ecc4
http://www.reibang.com/p/cd85098cca39
NIO-Buffer
http://www.reibang.com/p/fb832bc2cc32
Java 8 元數(shù)據(jù)空間
http://blog.csdn.net/zhushuai1221/article/details/52122880