JVM內(nèi)存模型
Java虛擬機(Java Virtual Machine=JVM)的內(nèi)存空間分為五個部分粤攒,分別是:
1. 程序計數(shù)器
2. Java虛擬機棧
3. 本地方法棧
4. 堆
5. 方法區(qū)百炬。
下面對這五個區(qū)域展開深入的介紹。
1. 程序計數(shù)器
1.1. 什么是程序計數(shù)器诫咱?
程序計數(shù)器是一塊較小的內(nèi)存空間溃列,可以把它看作當(dāng)前線程正在執(zhí)行的字節(jié)碼的行號指示器。也就是說困乒,程序計數(shù)器里面記錄的是當(dāng)前線程正在執(zhí)行的那一條字節(jié)碼指令的地址蝗肪。
注:但是袜爪,如果當(dāng)前線程正在執(zhí)行的是一個本地方法,那么此時程序計數(shù)器為空薛闪。
1.2. 程序計數(shù)器的作用
程序計數(shù)器有兩個作用:
- 字節(jié)碼解釋器通過改變程序計數(shù)器來依次讀取指令辛馆,從而實現(xiàn)代碼的流程控制,如:順序執(zhí)行豁延、選擇怀各、循環(huán)、異常處理术浪。
- 在多線程的情況下瓢对,程序計數(shù)器用于記錄當(dāng)前線程執(zhí)行的位置,從而當(dāng)線程被切換回來的時候能夠知道該線程上次運行到哪兒了胰苏。
1.3. 程序計數(shù)器的特點
- 是一塊較小的存儲空間
- 線程私有硕蛹。每條線程都有一個程序計數(shù)器。
- 是唯一一個不會出現(xiàn)OutOfMemoryError的內(nèi)存區(qū)域。
- 生命周期隨著線程的創(chuàng)建而創(chuàng)建法焰,隨著線程的結(jié)束而死亡秧荆。
2. Java虛擬機棧(JVM Stack)
2.1. 什么是Java虛擬機棧?
Java虛擬機棧是描述Java方法運行過程的內(nèi)存模型埃仪。
Java虛擬機棧會為每一個即將運行的Java方法創(chuàng)建一塊叫做“棧幀”的區(qū)域乙濒,這塊區(qū)域用于存儲該方法在運行過程中所需要的一些信息,這些信息包括:
- 局部變量表
存放基本數(shù)據(jù)類型變量卵蛉、引用類型的變量颁股、returnAddress類型的變量。- 操作數(shù)棧
- 動態(tài)鏈接
- 方法出口信息
- 等
當(dāng)一個方法即將被運行時傻丝,Java虛擬機棧首先會在Java虛擬機棧中為該方法創(chuàng)建一塊“棧幀”甘有,棧幀中包含局部變量表、操作數(shù)棧葡缰、動態(tài)鏈接亏掀、方法出口信息等。當(dāng)方法在運行過程中需要創(chuàng)建局部變量時泛释,就將局部變量的值存入棧幀的局部變量表中滤愕。
當(dāng)這個方法執(zhí)行完畢后,這個方法所對應(yīng)的棧幀將會出棧怜校,并釋放內(nèi)存空間间影。
注意:人們常說,Java的內(nèi)存空間分為“椌禄”和“堆”,棧中存放局部變量蔓搞,堆中存放對象胰丁。
這句話不完全正確!這里的“堆”可以這么理解喂分,但這里的“椊跤梗”只代表了Java虛擬機棧中的局部變量表部分。真正的Java虛擬機棧是由一個個棧幀組成蒲祈,而每個棧幀中都擁有:局部變量表甘萧、操作數(shù)棧、動態(tài)鏈接梆掸、方法出口信息扬卷。
2.2. Java虛擬機棧的特點
- 局部變量表的創(chuàng)建是在方法被執(zhí)行的時候,隨著棧幀的創(chuàng)建而創(chuàng)建酸钦。而且怪得,局部變量表的大小在編譯時期就確定下來了,在創(chuàng)建的時候只需分配事先規(guī)定好的大小即可。此外徒恋,在方法運行的過程中局部變量表的大小是不會發(fā)生改變的蚕断。
- Java虛擬機棧會出現(xiàn)兩種異常:StackOverFlowError和OutOfMemoryError。
a) StackOverFlowError:
若Java虛擬機棧的內(nèi)存大小不允許動態(tài)擴展入挣,那么當(dāng)線程請求棧的深度超過當(dāng)前Java虛擬機棧的最大深度的時候亿乳,就拋出StackOverFlowError異常.
b) OutOfMemoryError:
若Java虛擬機棧的內(nèi)存大小允許動態(tài)擴展,且當(dāng)線程請求棧時內(nèi)存用完了径筏,無法再動態(tài)擴展了葛假,此時拋出OutOfMemoryError異常。 - Java虛擬機棧也是線程私有的匠璧,每個線程都有各自的Java虛擬機棧桐款,而且隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的死亡而死亡夷恍。
注:StackOverFlowError和OutOfMemoryError的異同魔眨?
StackOverFlowError表示當(dāng)前線程申請的棧超過了事先定好的棧的最大深度,但內(nèi)存空間可能還有很多酿雪。
而OutOfMemoryError是指當(dāng)線程申請棧時發(fā)現(xiàn)棧已經(jīng)滿了遏暴,而且內(nèi)存也全都用光了。
3. 本地方法棧
3.1. 什么是本地方法棧指黎?
本地方法棧和Java虛擬機棧實現(xiàn)的功能類似朋凉,只不過本地方法區(qū)是本地方法運行的內(nèi)存模型。
本地方法被執(zhí)行的時候醋安,在本地方法棧也會創(chuàng)建一個棧幀杂彭,用于存放該本地方法的局部變量表、操作數(shù)棧吓揪、動態(tài)鏈接亲怠、出口信息。
方法執(zhí)行完畢后相應(yīng)的棧幀也會出棧并釋放內(nèi)存空間柠辞。
也會拋出StackOverFlowError和OutOfMemoryError異常团秽。
4. 堆
4.1. 什么是堆?
堆是用來存放對象的內(nèi)存空間叭首。
幾乎所有的對象都存儲在堆中习勤。
4.2. 堆的特點
- 線程共享
整個Java虛擬機只有一個堆,所有的線程都訪問同一個堆焙格。而程序計數(shù)器图毕、Java虛擬機棧、本地方法棧都是一個線程對應(yīng)一個的眷唉。- 在虛擬機啟動時創(chuàng)建
- 垃圾回收的主要場所吴旋。
- 可以進一步細(xì)分為:新生代损肛、老年代。
新生代又可被分為:Eden荣瑟、From Survior治拿、To Survior。
不同的區(qū)域存放具有不同生命周期的對象笆焰。這樣可以根據(jù)不同的區(qū)域使用不同的垃圾回收算法劫谅,從而更具有針對性,從而更高效嚷掠。- 堆的大小既可以固定也可以擴展捏检,但主流的虛擬機堆的大小是可擴展的,因此當(dāng)線程請求分配內(nèi)存不皆,但堆已滿贯城,且內(nèi)存已滿無法再擴展時,就拋出OutOfMemoryError霹娄。
5. 方法區(qū)
5.1. 什么是方法區(qū)能犯?
Java虛擬機規(guī)范中定義方法區(qū)是堆的一個邏輯部分。
方法區(qū)中存放已經(jīng)被虛擬機加載的類信息犬耻、常量踩晶、靜態(tài)變量、即時編譯器編譯后的代碼等枕磁。
5.2. 方法區(qū)的特點
- 線程共享
方法區(qū)是堆的一個邏輯部分渡蜻,因此和堆一樣,都是線程共享的计济。整個虛擬機中只有一個方法區(qū)茸苇。- 永久代
方法區(qū)中的信息一般需要長期存在,而且它又是堆的邏輯分區(qū)沦寂,因此用堆的劃分方法学密,我們把方法區(qū)稱為老年代。- 內(nèi)存回收效率低
方法區(qū)中的信息一般需要長期存在凑队,回收一遍內(nèi)存之后可能只有少量信息無效则果。
對方法區(qū)的內(nèi)存回收的主要目標(biāo)是:對常量池的回收 和 對類型的卸載幔翰。- Java虛擬機規(guī)范對方法區(qū)的要求比較寬松漩氨。
和堆一樣,允許固定大小遗增,也允許可擴展的大小叫惊,還允許不實現(xiàn)垃圾回收。
5.3. 什么是運行時常量池做修?
方法區(qū)中存放三種數(shù)據(jù):類信息霍狰、常量抡草、靜態(tài)變量、即時編譯器編譯后的代碼蔗坯。其中常量存儲在運行時常量池中康震。
我們一般在一個類中通過public static final來聲明一個常量。這個類被編譯后便生成Class文件宾濒,這個類的所有信息都存儲在這個class文件中腿短。
當(dāng)這個類被Java虛擬機加載后,class文件中的常量就存放在方法區(qū)的運行時常量池中绘梦。而且在運行期間橘忱,可以向常量池中添加新的常量。如:String類的intern()方法就能在運行期間向常量池中添加字符串常量卸奉。
當(dāng)運行時常量池中的某些常量沒有被對象引用钝诚,同時也沒有被變量引用,那么就需要垃圾收集器回收榄棵。
6. 直接內(nèi)存
接內(nèi)存是除Java虛擬機之外的內(nèi)存凝颇,但也有可能被Java使用。
在NIO中引入了一種基于通道和緩沖的IO方式秉继。它可以通過調(diào)用本地方法直接分配Java虛擬機之外的內(nèi)存祈噪,然后通過一個存儲在Java堆中的DirectByteBuffer對象直接操作該內(nèi)存,而無需先將外面內(nèi)存中的數(shù)據(jù)復(fù)制到堆中再操作尚辑,從而提升了數(shù)據(jù)操作的效率辑鲤。
直接內(nèi)存的大小不受Java虛擬機控制,但既然是內(nèi)存杠茬,當(dāng)內(nèi)存不足時就會拋出OOM異常月褥。
綜上所述
- Java虛擬機的內(nèi)存模型中一共有兩個“棧”瓢喉,分別是:Java虛擬機棧和本地方法棧宁赤。
兩個“棧”的功能類似栓票,都是方法運行過程的內(nèi)存模型决左。并且兩個“棧”內(nèi)部構(gòu)造相同走贪,都是線程私有佛猛。
只不過Java虛擬機棧描述的是Java方法運行過程的內(nèi)存模型,而本地方法棧是描述Java本地方法運行過程的內(nèi)存模型坠狡。 - Java虛擬機的內(nèi)存模型中一共有兩個“堆”继找,一個是原本的堆,一個是方法區(qū)逃沿。方法區(qū)本質(zhì)上是屬于堆的一個邏輯部分婴渡。堆中存放對象幻锁,方法區(qū)中存放類信息、常量边臼、靜態(tài)變量哄尔、即時編譯器編譯的代碼。
- 堆是Java虛擬機中最大的一塊內(nèi)存區(qū)域柠并,也是垃圾收集器主要的工作區(qū)域究飞。
- 程序計數(shù)器、Java虛擬機棧堂鲤、本地方法棧是線程私有的亿傅,即每個線程都擁有各自的程序計數(shù)器、Java虛擬機棧瘟栖、本地方法區(qū)葵擎。并且他們的生命周期和所屬的線程一樣。
而堆半哟、方法區(qū)是線程共享的酬滤,在Java虛擬機中只有一個堆、一個方法棧寓涨。并在JVM啟動的時候就創(chuàng)建盯串,JVM停止才銷毀