JVM-java內(nèi)存區(qū)域與內(nèi)存溢出異常
1 說明
java 與 c++之間有一堵由內(nèi)存動態(tài)分配和垃圾回收技術(shù)所圍成的高墻呻率,墻外的人想進(jìn)來躏将, 墻內(nèi)的人想出去锄弱。然而java的使用者就是這些墻里的人。這篇文章就是介紹java虛擬機(jī)內(nèi)存的各個區(qū)域祸憋,講述這些區(qū)域的作用会宪,服務(wù)對象以及其中可能產(chǎn)生的問題。從這里蚯窥,我們開始進(jìn)行翻墻工作掸鹅。然而請注意,墻的那邊是高能區(qū)......
2 運(yùn)行時數(shù)據(jù)區(qū)域
java虛擬機(jī)在執(zhí)行java程序過程中拦赠,會把它管理的內(nèi)存分成若干個不同的數(shù)據(jù)區(qū)巍沙。有的區(qū)域鎖著虛擬機(jī)進(jìn)程的啟動而存在,有些是依賴用戶進(jìn)程建立與銷毀荷鼠。在java7 的虛擬機(jī)規(guī)范中規(guī)定句携,虛擬機(jī)所管理的內(nèi)存將會包括如下幾個運(yùn)行時數(shù)據(jù)區(qū)域。下面就是java虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū):
2.1 程序計(jì)數(shù)器
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間允乐,在虛擬機(jī)概念模型里矮嫉,程序計(jì)數(shù)器的值是為了給字節(jié)碼解釋器提給選取嚇一跳需要執(zhí)行的字節(jié)碼只能提供幫助的。在分支牍疏,循環(huán)蠢笋,跳轉(zhuǎn),異常處理鳞陨,線程恢復(fù)都需要依賴這個計(jì)數(shù)器昨寞。因?yàn)槌绦蛴?jì)數(shù)器能幫助線程跳轉(zhuǎn)和恢復(fù):java虛擬機(jī)執(zhí)行多線程是采用輪流切換的機(jī)制,因此為了在切換回來的時候知道在哪里繼續(xù)執(zhí)行,所以才用程序計(jì)數(shù)器编矾。
由此可知熟史,程序計(jì)數(shù)器是線程私有的內(nèi)存區(qū)>程序技術(shù)器也是唯一一個沒有規(guī)定任何OOM(OutOfMemoryError)情況的區(qū)域馁害。
2.2 java虛擬機(jī)棧
java虛擬機(jī)棧是描述java方法執(zhí)行的內(nèi)存模型窄俏,每個方法執(zhí)行時候都會建立一個“棧幀”用于存儲:局部變量,操作數(shù)棧碘菜,動態(tài)鏈接凹蜈,方法的返回信息等。每一個方法的調(diào)用都是一個棧幀入棧到出棧的過程忍啸。經(jīng)常有人把java內(nèi)存區(qū)域分成“堆”和“椦鎏梗”,然而java的內(nèi)存區(qū)域要遠(yuǎn)遠(yuǎn)復(fù)雜的多计雌,而大家口中的“椙幕危”就是這里的“java虛擬機(jī)棧”或者更小一點(diǎn)凿滤,是虛擬機(jī)棧里的“局部變量表”部分妈橄。局部變量表存儲了編譯期就可以知道的基本數(shù)據(jù)類型,引用對象翁脆,和returnAddress類型眷蚓。局部變量表的內(nèi)存空間在編譯期就已經(jīng)完成分配了。當(dāng)進(jìn)入一個方法時候反番,這個方法余姚在幀中非配多大的局部變量空間是確定的了(大家可以想一下沙热,方法主要是由什么組成的,不就是局部變量么罢缸?既然都已經(jīng)知道要用什么局部變量了篙贸,那么這個內(nèi)存空間豈不是已經(jīng)確定了?)在java虛擬機(jī)規(guī)范中枫疆,對虛擬機(jī)棧規(guī)定了兩種異常情況:
- StackOverflowError: 如果線程請求的棧深度大于虛擬機(jī)所允許的深度爵川,則拋出棧溢出錯誤。
- OOM 如果虛擬機(jī)棧的內(nèi)存無法繼續(xù)擴(kuò)展养铸,則拋出內(nèi)存超出錯誤雁芙。
有上可知:java虛擬機(jī)棧也是線程私有的
2.3 本地方法棧
本地方法棧與虛擬機(jī)棧發(fā)揮的作用相似,不過虛擬機(jī)棧調(diào)用的是java的方法钞螟,而本地方法棧調(diào)用的是Native方法兔甘。因?yàn)閖ava虛擬機(jī)規(guī)范中并沒有規(guī)定“本地方法棧”要用什么語言實(shí)現(xiàn)鳞滨,所以甚至有些虛擬機(jī)都把“本地方法椂幢海”與“虛擬機(jī)棧”合二為一。與虛擬機(jī)棧一樣澡匪,同樣會拋出上面的兩個異常熔任。
2.4 java堆
java堆是java虛擬機(jī)所管理的內(nèi)存中的最大的一塊。此內(nèi)存的唯一目的就是存放對象實(shí)例唁情!幾乎所有的對象實(shí)例都在這里分配內(nèi)存疑苔。“java堆”是java垃圾回收機(jī)制管理的主要區(qū)域甸鸟,因此也被稱為“GC堆”惦费。在內(nèi)存回收的角度來看,由于算法多采用的是分代回收抢韭,所以又被分為:新生代薪贫,老年代。 在細(xì)致一點(diǎn)的可以分為:Eden區(qū)刻恭, From survivor空間和 To survivor空間瞧省。在內(nèi)存分配的角度來說,java堆可以分成多個線程私有的分配緩沖區(qū)TLAB(Thread Local Allocation Buffer)鳍贾。
java堆 是內(nèi)存共享的
2.5 方法區(qū)
方法區(qū)與java堆一樣鞍匾,用來存儲已經(jīng)被虛擬機(jī)加載的類信息、常量贾漏、靜態(tài)變量候学、及時編譯后的代碼數(shù)據(jù)。(那為什么叫做方法區(qū)呢纵散?很好奇)方法區(qū)很多人有稱之為“永久代”梳码,雖然這么稱呼并不完全準(zhǔn)確。原來的字符串常量池在永久代里伍掀,但是現(xiàn)在看來這么設(shè)計(jì)并不是一個好的作法(因?yàn)镾tring.intern()方法掰茶,可以動態(tài)的向常量池用添加字符串,如果永久代不清理的話/換句話說清理起來很難蜜笤,就會導(dǎo)致內(nèi)存泄露濒蒋。)
也是線程共享的
3 對象的創(chuàng)建
java是一門面向?qū)ο蟮恼Z言。在java程序運(yùn)行過程中把兔,無時無刻不在有對象被創(chuàng)建沪伙。在語言成面上,僅僅是一個new而已县好。但是在虛擬機(jī)中围橡,又是一個怎樣的景象呢?
(1) 當(dāng)虛擬機(jī)遇到一條new指令的適合缕贡,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用翁授,并且檢查這個符號引用代表的類是否已經(jīng)被加載拣播、解析、初始化過的收擦。如果沒有贮配,那必須先進(jìn)性相應(yīng)的類的初始化。
-
(2) 當(dāng)類加載檢查后塞赂,接下來虛擬機(jī)為新生對象分配內(nèi)存泪勒,對象所需的內(nèi)存大小在類加載后便可以完全確定下來。然后分配對象的任務(wù)就等同于把一塊大小確定的內(nèi)存劃分出來:* 指針碰撞 * 如果內(nèi)存區(qū)域是卻對規(guī)整的减途,所有已經(jīng)分配的內(nèi)存在一起酣藻,沒有分配的內(nèi)存在一起。則就可以在中間設(shè)置一個指針鳍置,當(dāng)創(chuàng)建新的對象時候,就把指針向沒有分配的地方移動即可送淆。
- Serial税产、ParNew 等帶有Compact過程的收集器,則采用的是指針碰撞偷崩。
- 空閑列表: 如果內(nèi)存是不規(guī)整的辟拷,則用一張表記錄哪些是分配的,哪些是沒有分配的阐斜。在沒有分配的內(nèi)存中取出一塊比較大的內(nèi)存使用衫冻,并且記錄下情況。
- 使用CMS這種基于Mark-Sweep算法的收集器時谒出。
(3) 上面的情況是在單線程的情況下隅俘,如果在多線程的情況下有如下的解決辦法:一種是對分配內(nèi)存的動作進(jìn)行同步---采用CAS+失敗重試的方式保證操作的原子性。第二種是在每個線程上都分配一塊內(nèi)存笤喳,自己線程的對象为居,在自己的內(nèi)存上進(jìn)行分配(TLAB)。
4 一個有意思的現(xiàn)象
public class RuntimeConstantPoolOOM{
public static void main(String[] args){
String str1 = new StringBuffer("計(jì)算機(jī)").append("軟件").toString();
System.out.println(str1.intern() == str1); String str2 = new StringBuffer("ja").append("va").toString();
System.out.println(str2.intern() == str2)
}
}
上面的這段代碼杀狡,如果在1.6執(zhí)行蒙畴,則都輸出false:因?yàn)镾tringBuffer是在堆中申請的內(nèi)存,String.intern是將對象復(fù)制到方法區(qū)(常量池)中呜象。所以兩個不是指向一個地方膳凝; 如果是在1.7中,則第一個輸出true蹬音, 第二個輸出false楼入, 因?yàn)?.7中的intern方法不再是復(fù)制對象,而是記錄復(fù)制的引用。所以第一個輸出true,而java這里并不是第一次出現(xiàn)衫画,所以常量池中的引用并不是內(nèi)存中的引用,所以輸出false;
內(nèi)容學(xué)習(xí)自GC博客