JVM(一)---- 總結(jié)與專題目錄
JVM(二)----Java運(yùn)行時數(shù)據(jù)區(qū)域
JVM(三)----垃圾收集算法及Safe Point介紹
JVM(四)----HotSpot的垃圾收集器與內(nèi)存分配回收策略
JVM(五)----虛擬機(jī)類加載機(jī)制
本文結(jié)構(gòu)如下:
1.運(yùn)行時數(shù)據(jù)區(qū)域總覽
2.每一部分介紹
3.Hotspot永久代在1.8中移除的總結(jié)
首先理解一個概念,就是虛擬機(jī)實(shí)例跟束。當(dāng)啟動一個Java程序時,一個虛擬機(jī)實(shí)例也就誕生了甚疟。當(dāng)該程序關(guān)閉退出揽祥,這個虛擬機(jī)實(shí)例也就隨之消亡俐末。如果同一臺計(jì)算機(jī)上同時運(yùn)行三個Java程序,將得到三個Java虛擬機(jī)實(shí)例。每個Java程序都運(yùn)行于它自己的Java虛擬機(jī)實(shí)例中馁筐。
一盟迟、總覽
Java虛擬機(jī)所管理的內(nèi)存將會包括一下幾個運(yùn)行時數(shù)據(jù)區(qū)域:
注意:線程私有和共享的區(qū)域辖众。
二、介紹每個部分
2.1程序計(jì)數(shù)器
- 作用:
記錄當(dāng)前線程所執(zhí)行到的字節(jié)碼的行號。字節(jié)碼解釋器工作的時候就是通過改變這個計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。 - 意義:
JVM的多線程是通過線程輪流切換并分配處理器來實(shí)現(xiàn)的,對于我們來說的并行事實(shí)上一個處理器也只會執(zhí)行一條線程中的指令瓢捉。所以迂卢,為了保證各線程指令的安全順利執(zhí)行,每條線程都有獨(dú)立的私有的程序計(jì)數(shù)器腾降。 - 存儲內(nèi)容
當(dāng)線程中執(zhí)行的是一個Java方法時奸晴,程序計(jì)數(shù)器中記錄的是正在執(zhí)行的線程的虛擬機(jī)字節(jié)碼指令的地址辕录。
當(dāng)線程中執(zhí)行的是一個本地方法時蚣旱,程序計(jì)數(shù)器中的值為空异吻。 - 可能出現(xiàn)異常
此內(nèi)存區(qū)域是唯一一個在JVM上不會發(fā)生內(nèi)存溢出異常(OutOfMemoryError)的區(qū)域雷猪。
2.2Java虛擬機(jī)棧
- 作用
描述Java方法執(zhí)行的內(nèi)存模型验夯,也是線程私有的,生命周期與線程相同。每個方法在執(zhí)行的同時都會開辟一段內(nèi)存區(qū)域用于存放方法運(yùn)行時所需的數(shù)據(jù)泼掠,稱為棧幀,一個棧幀包含如:局部變量表吝梅、操作數(shù)棧苏携、動態(tài)鏈接装蓬、方法出口等信息矛物。 - 意義
每個方法從調(diào)用到執(zhí)行結(jié)束忆首,就對應(yīng)著一個棧幀在虛擬機(jī)棧中入棧和出棧的整個過程浸锨。 - 存儲內(nèi)容
局部變量表(編譯期可知的各種基本數(shù)據(jù)類型控乾、引用類型和指向一條字節(jié)碼指令的returnAddress類型)、操作數(shù)棧躲雅、動態(tài)鏈接鼎姊、方法出口等信息。
值得注意的是:局部變量表所需的內(nèi)存空間在編譯期間完成分配。在方法運(yùn)行的階段是不會改變局部變量表的大小的相寇。 - 可能出現(xiàn)的異常
如果線程請求的棧深度大于虛擬機(jī)所允許的深度慰于,將拋出StackOverflowError異常。
如果在動態(tài)擴(kuò)展內(nèi)存的時候無法申請到足夠的內(nèi)存唤衫,就會拋出OutOfMemoryError異常婆赠。
2.3本地方法棧
- 作用
為JVM所調(diào)用到的Native方法(即本地方法)服務(wù)。 - 可能出現(xiàn)的異常
和虛擬機(jī)棧出現(xiàn)的異常很相像佳励。也是StackOverflowError異常和OutOfMemoryError異常休里。
2.4 Java堆(Heap)
- 作用
所有線程共享一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建妙黍,此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例。 - 意義
1瞧剖、存儲對象實(shí)例拭嫁,更好地分配內(nèi)存。
2抓于、垃圾回收(Garbage Collection)做粤。堆是垃圾收集器管理的主要區(qū)域。更好地回收內(nèi)存捉撮。 - 存儲內(nèi)容
存放對象實(shí)例驮宴,幾乎所有的對象實(shí)例都在這里進(jìn)行分配。堆可以處于物理上不連續(xù)的內(nèi)存空間呕缭,只要邏輯上是連續(xù)的就可以。
值得注意的是:在JIT編譯器等技術(shù)的發(fā)展下修己,所有對象都在堆上進(jìn)行分配已變得不那么絕對恢总。 - 可能出現(xiàn)的異常
實(shí)現(xiàn)堆可以是固定大小的,也可以通過設(shè)置配置文件設(shè)置該為可擴(kuò)展的睬愤。
如果堆上沒有內(nèi)存進(jìn)行分配片仿,并無法進(jìn)行擴(kuò)展時,將會拋出OutOfMemoryError異常尤辱。
2.5方法區(qū)
- 作用
用于存儲運(yùn)行時常量池砂豌、已被虛擬機(jī)加載的類信息、常量光督、靜態(tài)變量阳距、即時編譯器編譯后的代碼等數(shù)據(jù)。 - 意義
對運(yùn)行時常量池结借、常量筐摘、靜態(tài)變量等數(shù)據(jù)做出了規(guī)定。 - 存儲內(nèi)容
運(yùn)行時常量池(具有動態(tài)性)、已被虛擬機(jī)加載的類信息咖熟、常量圃酵、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)馍管。 - 可能出現(xiàn)的異常
當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時郭赐,將拋出OutOfMemoryError異常。
三确沸、關(guān)于永久代在1.8中移除的總結(jié)
絕大部分 Java 程序員應(yīng)該都見過 "java.lang.OutOfMemoryError: PermGen space "這個異常捌锭。這里的 “PermGen space”其實(shí)指的就是方法區(qū)。不過方法區(qū)和“PermGen space”又有著本質(zhì)的區(qū)別张惹。前者是 JVM 的規(guī)范舀锨,而后者則是 JVM 規(guī)范的一種實(shí)現(xiàn),可以這么理解:方法區(qū)和永久帶的關(guān)系就像Java中接口和實(shí)現(xiàn)接口的類之間的關(guān)系一樣宛逗。并且只有 HotSpot 才有 “PermGen space”坎匿,而對于其他類型的虛擬機(jī),如 JRockit(Oracle)雷激、J9(IBM) 并沒有“PermGen space”替蔬。由于方法區(qū)主要存儲類的相關(guān)信息,所以對于動態(tài)生成類的情況比較容易出現(xiàn)永久代的內(nèi)存溢出屎暇。
在 JDK 1.8 中承桥, HotSpot 已經(jīng)沒有 “PermGen space”這個區(qū)間了,取而代之是一個叫做 Metaspace(元空間) 的東西根悼。下面我們就來看看 Metaspace 與 PermGen space 的區(qū)別凶异。
其實(shí),移除永久代的工作從JDK1.7就開始了挤巡。JDK1.7中剩彬,存儲在永久代的部分?jǐn)?shù)據(jù)就已經(jīng)轉(zhuǎn)移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中矿卑,并沒完全移除喉恋,譬如符號引用(Symbols)轉(zhuǎn)移到了native heap;字面量(interned strings)轉(zhuǎn)移到了java heap母廷;類的靜態(tài)變量(class statics)轉(zhuǎn)移到了java heap轻黑。我們可以通過一段程序來比較 JDK 1.6 與 JDK 1.7及 JDK 1.8 的區(qū)別,以字符串常量為例:
package com.paddx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class StringOomMock {
static String base = "string";
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i=0;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern());
}
}
}
這段程序不斷的生成新的字符串琴昆,并且通過intern方法將字符串放到字符串常量池中氓鄙,這樣可以比較快速的消耗內(nèi)存。我們通過 JDK 1.6业舍、JDK 1.7 和 JDK 1.8 分別運(yùn)行:
JDK 1.6 的運(yùn)行結(jié)果:
JDK 1.7的運(yùn)行結(jié)果:
JDK 1.8的運(yùn)行結(jié)果:
從上述結(jié)果可以看出玖详,JDK 1.6下把介,會出現(xiàn)“PermGen Space”的內(nèi)存溢出,而在 JDK 1.7和 JDK 1.8 中蟋座,會出現(xiàn)堆內(nèi)存溢出拗踢,并且 JDK 1.8中 PermSize 和 MaxPermGen 已經(jīng)無效。因此向臀,可以大致驗(yàn)證 JDK 1.7 和 1.8 將字符串常量由永久代轉(zhuǎn)移到堆中巢墅,并且 JDK 1.8 中已經(jīng)不存在永久代的結(jié)論。
元空間的本質(zhì)和永久代類似券膀,都是對JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)君纫。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存芹彬。因此蓄髓,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制舒帮,但可以通過以下參數(shù)來指定元空間的大谢岷取:
-XX:MetaspaceSize,初始空間大小玩郊,達(dá)到該值就會觸發(fā)垃圾收集進(jìn)行類型卸載肢执,同時GC會對該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值译红;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時侦厚,適當(dāng)提高該值耻陕。
-XX:MaxMetaspaceSize刨沦,最大空間,默認(rèn)是沒有限制的已卷。
除了上面兩個指定大小的選項(xiàng)以外淳蔼,還有兩個與 GC 相關(guān)的屬性:
-XX:MinMetaspaceFreeRatio,在GC之后鹉梨,最小的Metaspace剩余空間容量的百分比讳癌,減少為分配空間所導(dǎo)致的垃圾收集
-XX:MaxMetaspaceFreeRatio存皂,在GC之后晌坤,最大的Metaspace剩余空間容量的百分比逢艘,減少為釋放空間所導(dǎo)致的垃圾收集。
最后總結(jié)一下為什么移除永久帶而使用元空間的原因吧:
- 字符串存在永久代中骤菠,容易出現(xiàn)性能問題和內(nèi)存溢出它改。
- 類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難商乎,太小容易出現(xiàn)永久代溢出央拖,太大則容易導(dǎo)致老年代溢出。
- 永久代會為 GC 帶來不必要的復(fù)雜度鹉戚,并且回收效率偏低鲜戒。
- Oracle 可能會將HotSpot 與 JRockit 合二為一。