自動(dòng)內(nèi)存管理機(jī)制
java內(nèi)存區(qū)域與內(nèi)存溢出異常
1.java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)
- 程序計(jì)數(shù)器(Program Counter Register)
它是一塊較小的內(nèi)存空間累贤,它的作用可以看做是當(dāng)先線程所執(zhí)行的字節(jié)碼的信號(hào)指示器襟交。
每一條JVM線程都有自己的PC寄存器,各條線程之間互不影響检诗,獨(dú)立存儲(chǔ),這類內(nèi)存區(qū)域被稱為“線程私有”內(nèi)存
在任意時(shí)刻,一條JVM線程只會(huì)執(zhí)行一個(gè)方法的代碼吐葵。該方法稱為該線程的當(dāng)前方法(Current Method)
如果該方法是java方法,那PC寄存器保存JVM正在執(zhí)行的字節(jié)碼指令的地址
如果該方法是native桥氏,那PC寄存器的值是undefined温峭。
此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域。
- Java虛擬機(jī)棧(Java Virtual Machine Stack)
與PC寄存器一樣字支,Java虛擬機(jī)棧也是線程私有的凤藏。每一個(gè)JVM線程都有自己的java虛擬機(jī)棧,這個(gè)棧與線程同時(shí)創(chuàng)建堕伪,它的生命周期與線程相同揖庄。
虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候都會(huì)同時(shí)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表、操作數(shù)棧欠雌、動(dòng)態(tài)鏈接蹄梢、方法出口等信息。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過(guò)程就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程富俄。
JVM stack 可以被實(shí)現(xiàn)成固定大小禁炒,也可以根據(jù)計(jì)算動(dòng)態(tài)擴(kuò)展。
如果采用固定大小的JVM stack設(shè)計(jì)蛙酪,那么每一條線程的JVM Stack容量應(yīng)該在線程創(chuàng)建時(shí)獨(dú)立地選定齐苛。JVM實(shí)現(xiàn)應(yīng)該提供調(diào)節(jié)JVM Stack初始容量的手段;如果采用動(dòng)態(tài)擴(kuò)展和收縮的JVM Stack方式桂塞,應(yīng)該提供調(diào)節(jié)最大凹蜂、最小容量的手段。
如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度將拋出StackOverflowError;
如果JVM Stack可以動(dòng)態(tài)擴(kuò)展玛痊,但是在嘗試擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存時(shí)拋出OutOfMemoryError汰瘫。
- 本地方法棧(Native Method Stack)
本地方法棧與虛擬機(jī)棧作用相似,后者為虛擬機(jī)執(zhí)行Java方法服務(wù)擂煞,而前者為虛擬機(jī)用到的Native方法服務(wù)混弥。
虛擬機(jī)規(guī)范對(duì)于本地方法棧中方法使用的語(yǔ)言,使用方式和數(shù)據(jù)結(jié)構(gòu)沒(méi)有強(qiáng)制規(guī)定对省,甚至有的虛擬機(jī)(比如HotSpot)直接把二者合二為一蝗拿。
這玩意兒拋出的異常跟上面的虛擬機(jī)棧一樣。
- Java堆(Java Heap)
虛擬機(jī)管理的內(nèi)存中最大的一塊蒿涎,同時(shí)也是被所有線程所共享的哀托,它在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,這貨存在的意義就是存放對(duì)象實(shí)例劳秋,幾乎所有的對(duì)象實(shí)例以及數(shù)組都要在這里分配內(nèi)存仓手。這里面的對(duì)象被自動(dòng)管理,也就是俗稱的GC(Garbage Collector)所管理玻淑。用就是了嗽冒,有GC扛著呢,不用操心銷毀回收的事兒补履。
Java堆的容量可以是固定大小添坊,也可以隨著需求動(dòng)態(tài)擴(kuò)展(-Xms和-Xmx),并在不需要過(guò)多空間時(shí)自動(dòng)收縮干像。
Java堆所使用的內(nèi)存不需要保證是物理連續(xù)的帅腌,只要邏輯上是連續(xù)的即可。
JVM實(shí)現(xiàn)應(yīng)當(dāng)提供給程序員調(diào)節(jié)Java 堆初始容量的手段麻汰,對(duì)于可動(dòng)態(tài)擴(kuò)展和收縮的堆來(lái)說(shuō)速客,則應(yīng)當(dāng)提供調(diào)節(jié)其最大和最小容量的手段。
如果堆中沒(méi)有內(nèi)存完成實(shí)例分配并且堆也無(wú)法擴(kuò)展五鲫,就會(huì)拋OutOfMemoryError溺职。
- 方法區(qū)(Method Area)
跟堆一樣是被各個(gè)線程共享的內(nèi)存區(qū)域,用于存儲(chǔ)以被虛擬機(jī)加載的類信息位喂、常量浪耘、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)塑崖。雖然這個(gè)區(qū)域被虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分七冲,但是它的別名叫非堆,用來(lái)與堆做一下區(qū)別规婆。
方法區(qū)在虛擬機(jī)啟動(dòng)的時(shí)候創(chuàng)建澜躺。
方法區(qū)的容量可以是固定大小的蝉稳,也可以隨著程序執(zhí)行的需求動(dòng)態(tài)擴(kuò)展,并在不需要過(guò)多空間時(shí)自動(dòng)收縮掘鄙。
方法區(qū)在實(shí)際內(nèi)存空間中可以是不連續(xù)的耘戚。
Java虛擬機(jī)實(shí)現(xiàn)應(yīng)當(dāng)提供給程序員或者最終用戶調(diào)節(jié)方法區(qū)初始容量的手段,對(duì)于可以動(dòng)態(tài)擴(kuò)展和收縮方法區(qū)來(lái)說(shuō)操漠,則應(yīng)當(dāng)提供調(diào)節(jié)其最大收津、最小容量的手段。
當(dāng)方法區(qū)無(wú)法滿足內(nèi)存分配需求時(shí)就會(huì)拋OutOfMemoryError浊伙。
- 運(yùn)行時(shí)常量池(Runtime Constant Pool)
它是方法區(qū)的一部分撞秋。Class文件中除了有類的版本、字段嚣鄙、方法部服、接口等描述等信息外,還有一項(xiàng)信息是常量池(Constant Pool Table)拗慨,用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中奉芦。
Java虛擬機(jī)對(duì)Class文件的每一部分(自然也包括常量池)的格式都有嚴(yán)格的規(guī)定赵抢,每一個(gè)字節(jié)用于存儲(chǔ)哪種數(shù)據(jù)都必須符合規(guī)范上的要求,這樣才會(huì)被虛擬機(jī)認(rèn)可声功、裝載和執(zhí)行烦却。但對(duì)于運(yùn)行時(shí)常量池,Java虛擬機(jī)規(guī)范沒(méi)有做任何細(xì)節(jié)的要求先巴,不同的提供商實(shí)現(xiàn)的虛擬機(jī)可以按照自己的需要來(lái)實(shí)現(xiàn)這個(gè)內(nèi)存區(qū)域其爵。不過(guò),一般來(lái)說(shuō)伸蚯,除了保存Class文件中描述的符號(hào)引用外摩渺,還會(huì)把翻譯出來(lái)的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池中。
運(yùn)行時(shí)常量池相對(duì)于Class文件常量池的另外一個(gè)重要特征是具備動(dòng)態(tài)性剂邮,Java語(yǔ)言并不要求常量一定只能在編譯期產(chǎn)生摇幻,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中挥萌,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法绰姻。
既然運(yùn)行時(shí)常量池是方法區(qū)的一部分,自然會(huì)受到方法區(qū)內(nèi)存的限制引瀑,當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常狂芋。
2.hotspot虛擬機(jī)對(duì)象探秘
1.對(duì)象的創(chuàng)建
new->檢查這個(gè)指令參數(shù)是否能在常量池中定位到一個(gè)類的引用符號(hào)->判斷這個(gè)類是否被加載、解析憨栽、初始化過(guò)->加載檢查通過(guò)后帜矾,分配內(nèi)存(指針碰撞翼虫,空閑列表)
->對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理(CAS配上失敗重試、TLAB)->虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值->對(duì)對(duì)象進(jìn)行必要的設(shè)置
2.對(duì)象的內(nèi)存布局
還Hotspot虛擬機(jī)中黍特,對(duì)象的內(nèi)存中存儲(chǔ)的布局分為3塊區(qū)域:對(duì)象頭(header)蛙讥、實(shí)例數(shù)據(jù)(Instance Data)、對(duì)其填充(Padding)
Hotspot虛擬機(jī)的對(duì)象頭包括兩部分信息灭衷,第一部分用于存儲(chǔ)自身運(yùn)行時(shí)的數(shù)據(jù)次慢,例如:哈希碼、GC分代年齡翔曲、鎖狀態(tài)標(biāo)識(shí)迫像、線程持有鎖、偏向線程id瞳遍、偏向時(shí)間戳闻妓,這部分?jǐn)?shù)據(jù)數(shù)據(jù)長(zhǎng)度在32位和64位的虛擬機(jī)(未開啟指針壓縮)中分別為32bit和64bit,官方稱為’Mark word’掠械。對(duì)象需要存儲(chǔ)的運(yùn)行時(shí)的數(shù)據(jù)非常多由缆,已經(jīng)超出了32位、64位bitmap結(jié)構(gòu)所能記錄的限度猾蒂,但是對(duì)象頭信息是與對(duì)象自身定義的數(shù)據(jù)無(wú)關(guān)額外的存儲(chǔ)成本髓棋,考慮到虛擬機(jī)的空間效率侠草,Mark work被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小空間內(nèi)存儲(chǔ)盡可能多的信息躺坟,他會(huì)根據(jù)對(duì)象狀態(tài)復(fù)用自己的存儲(chǔ)空間菱阵。
對(duì)象頭的另一部分是類型指針,即對(duì)象指向他的類元數(shù)據(jù)的指針蚊逢,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例层扶。如果對(duì)象是一個(gè)Java數(shù)組,那在對(duì)象中還必須有一塊用于記錄數(shù)組長(zhǎng)度的數(shù)據(jù)烙荷,因?yàn)樘摂M機(jī)可通過(guò)普通 Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小镜会,但是從數(shù)組的元數(shù)據(jù)中卻無(wú)法確定數(shù)組的大小。
接下來(lái)的實(shí)例數(shù)據(jù)是對(duì)象真正存儲(chǔ)的有效信息奢讨,也是在程序代碼中所定義的各種類型的字段內(nèi)容稚叹。無(wú)論是從父類繼承下來(lái)的還是在子類中定義的,都需要記錄下來(lái)拿诸。這部分的存儲(chǔ)順序會(huì)受到虛擬機(jī)分配策略參數(shù)和字段在Java源碼中定義順序的影響扒袖。Hotspot虛擬機(jī)的分配策略是相同寬度的字段總是被分配到一起。在滿足這個(gè)前提條件下亩码,在父類中定義的變量會(huì)出現(xiàn)在子類之前季率。
3.對(duì)象的訪問(wèn)定位
- 句柄
如果使用句柄訪問(wèn)的話,Java堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池描沟,reference中存儲(chǔ)的就是對(duì)象的句柄地址飒泻,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)的具體各自的地址信息鞭光。如圖1所示。
- 直接指針
如果使用直接指針訪問(wèn)的話泞遗,Java堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類型數(shù)據(jù)的相關(guān)信息惰许,reference中存儲(chǔ)的直接就是對(duì)象地址,如圖2所示史辙。
3.outofmemoryerror異常
1.java 堆溢出
2.虛擬機(jī)棧本地方法溢出
3.方法區(qū)和運(yùn)行時(shí)常量池溢出
4.本機(jī)直接內(nèi)存溢出