很多人會誤以為Java內(nèi)存區(qū)域和內(nèi)存模型是同一個東西访递,其實(shí)并不是鞋既。
Java內(nèi)存區(qū)域是指 JVM運(yùn)行時將數(shù)據(jù)分區(qū)域存儲 ,簡單的說就是不同的數(shù)據(jù)放在不同的地方棕兼。通常又叫 運(yùn)行時數(shù)據(jù)區(qū)域抵乓。
Java內(nèi)存模型(JMM)定義了程序中各個變量的訪問規(guī)則灾炭,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。
1田弥、Java內(nèi)存區(qū)域
1.8 之前:
Java內(nèi)存區(qū)域 1.8之前
JDK1.8(含)之后:
Java內(nèi)存區(qū)域 1.8
區(qū)別就是 1.8有一個元數(shù)據(jù)區(qū)替代方法區(qū)了偷厦。
JDK 1.7 其實(shí)是并沒完全移除方法區(qū)只泼,但是不會像1.6以前報 “java.lang.OutOfMemoryError: PermGen space
”卵洗,而是報 java.lang.OutOfMemoryError: Java heap space
1.7部分內(nèi)容(比如 常量池过蹂、靜態(tài)變量有方法區(qū)轉(zhuǎn)移到了堆)
那么榴啸,Java 8 中 PermGen 為什么被移出 HotSpot JVM 了?我總結(jié)了兩個主要原因(詳見:JEP 122: Remove the Permanent Generation):
- 由于 PermGen 內(nèi)存經(jīng)常會溢出勋功,引發(fā)惱人的 java.lang.OutOfMemoryError: PermGen库说,因此 JVM 的開發(fā)者希望這一塊內(nèi)存可以更靈活地被管理潜的,不要再經(jīng)常出現(xiàn)這樣的 OOM
- 移除 PermGen 可以促進(jìn) HotSpot JVM 與 JRockit VM 的融合,因?yàn)?JRockit 沒有永久代信不。
根據(jù)上面的各種原因抽活,PermGen 最終被移除,方法區(qū)移至 Metaspace丁逝,字符串常量移至 Java Heap梭姓。
下面逐一介紹一下jvm管轄的這幾種內(nèi)存區(qū)域誉尖。
2、程序計數(shù)器
程序計數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間萝衩,由于JVM可以并發(fā)執(zhí)行線程,因此會存在線程之間的切換千劈,而這個時候就程序計數(shù)器會記錄下當(dāng)前程序執(zhí)行到的位置墙牌,以便在其他線程執(zhí)行完畢后,恢復(fù)現(xiàn)場繼續(xù)執(zhí)行捉捅。
JVM會為每個線程分配一個程序計數(shù)器棒口,與線程的生命周期相同辜膝。
如果線程正在執(zhí)行的是應(yīng)該Java方法厂抖,這個計數(shù)器記錄的是正在執(zhí)行虛擬機(jī)字節(jié)碼指令的地址。
如果正在執(zhí)行的是Native方法七蜘,計數(shù)器的值則為空(undefined)
程序計數(shù)器是唯一一個在 Java 虛擬機(jī)規(guī)范中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域。
3扮念、Java虛擬機(jī)棧
虛擬機(jī)棧 描述的是 Java 方法執(zhí)行的內(nèi)存模型:
每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame扔亥,是方法運(yùn)行時的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu))用于存儲局部變量表谈为、操作數(shù)棧伞鲫、動態(tài)鏈接、方法出口等信息柒瓣。每一個方法從調(diào)用直至執(zhí)行完成的過程芙贫,就對應(yīng)著一個棧幀在虛擬機(jī)棧中入棧到出棧的過程傍药。
虛擬機(jī)棧是每個線程獨(dú)有的拐辽,隨著線程的創(chuàng)建而存在,線程結(jié)束而死亡菠劝。
在虛擬機(jī)棧內(nèi)存不夠的時候會OutOfMemoryError
赶诊,在線程運(yùn)行中需要更大的虛擬機(jī)棧時會出現(xiàn)StackOverFlowError
介袜。
虛擬機(jī)棧包含很多棧幀遇伞,每個方法執(zhí)行的同時會創(chuàng)建一個棧幀,棧幀又存儲了方法的局部變量表巍耗、操作數(shù)棧炬太、動態(tài)連接和方法返回地址等信息。
在活動線程中炒考,只有位于棧頂?shù)臈攀怯行У恼啵Q為當(dāng)前棧幀知给,與這個棧幀相關(guān)聯(lián)的方法稱為當(dāng)前方法涩赢。
1)局部變量表
局部變量表是存放方法參數(shù)和局部變量的區(qū)域。
全局變量是放在堆的怯邪,有兩次賦值的階段擎颖,一次在類加載的準(zhǔn)備階段观游,賦予系統(tǒng)初始值懂缕;另外一次在類加載的初始化階段王凑,賦予代碼定義的初始值索烹。
而局部變量沒有賦初始值是不能使用的百姓。
2)操作數(shù)棧
一個先入后出的棧。
當(dāng)一個方法剛剛開始執(zhí)行的時候旬迹,這個方法的操作數(shù)棧是空的奔垦,在方法的執(zhí)行過程中,會有各種字節(jié)碼指令往操作數(shù)棧中寫入和提取內(nèi)容惶岭,也就是出棧/入棧操作按灶。
3) 動態(tài)連接
每個棧幀都包含一個指向運(yùn)行時常量池中該棧幀所屬方法的引用阔逼。持有這個引用是為了支持方法調(diào)用過程中的動態(tài)連接(Dynamic Linking)嗜浮。
常量池可以便于指令的識別
public void methodA(){
}
public void methodB(){
methodA();//methodB()調(diào)用methodA(),先找到調(diào)用methodA()的版本符號危融,再變?yōu)橹苯右? }
方法調(diào)用并不等同于方法執(zhí)行,方法調(diào)用階段唯一的任務(wù)就是確定被調(diào)用方法的版本(即調(diào)用哪一個方法)辞居,這也是Java強(qiáng)大的擴(kuò)展能力瓦灶,在運(yùn)行期間才能確定目標(biāo)方法的直接引用贼陶。
所有方法調(diào)用中的目標(biāo)方法在Class文件里面都是一個常量池中的符號引用巧娱,在類加載的解析階段禁添,會將其中的一部分符號引用轉(zhuǎn)化為直接引用。
4)方法返回地址(方法出口)
返回分為 正常返回 和 異常退出芹啥。
無論何種退出情況叁征,都將返回至方法當(dāng)前被調(diào)用的位置,這也程序才能繼續(xù)執(zhí)行疏虫。
一般來說卧秘,方法正常退出時翅敌,調(diào)用者的PC計數(shù)器的值可以作為返回地址惕蹄,棧幀中會保存這個計數(shù)器值。
方法退出的過程相當(dāng)于彈出當(dāng)前棧幀遭顶。
4棒旗、本地方法棧
Java虛擬機(jī)棧是調(diào)用Java方法铣揉;本地方法棧是調(diào)用本地native方法餐曹,可以認(rèn)為是通過 JNI
(Java Native Interface) 直接調(diào)用本地 C/C++ 庫台猴,不受JVM控制卿吐。
本地方法棧也會拋出 StackOverflowError 和 OutOfMemoryError 異常
5嗡官、Java堆
Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建毯焕。此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例衍腥,幾乎所有的對象實(shí)例都在這里分配內(nèi)存磺樱。
堆是垃圾收集器管理的主要區(qū)域,又稱為“GC堆”婆咸,可以說是Java虛擬機(jī)管理的內(nèi)存中最大的一塊竹捉。
現(xiàn)在的虛擬機(jī)(包括HotSpot VM)都是采用分代回收算法。在分代回收的思想中尚骄, 把堆分為:新生代+老年代+永久代(1.8沒有了)块差; 新生代 又分為 Eden + From Survivor + To Survivor區(qū)倔丈。
6憨闰、方法區(qū)
方法區(qū)(Method Area)與 Java 堆一樣,是所有線程共享的內(nèi)存區(qū)域需五。
方法區(qū)用于存儲已經(jīng)被虛擬機(jī)加載的類信息(即加載類時需要加載的信息鹉动,包括版本、field宏邮、方法泽示、接口等信息)、final常量蜜氨、靜態(tài)變量械筛、編譯器即時編譯的代碼等。
方法區(qū)邏輯上屬于堆的一部分记劝,但是為了與堆進(jìn)行區(qū)分变姨,通常又叫“非堆”。
方法區(qū)比較重要的一部分是運(yùn)行時常量池(Runtime Constant Pool)厌丑,為什么叫運(yùn)行時常量池呢定欧?是因?yàn)檫\(yùn)行期間可能會把新的常量放入池中,比如說常見的String的intern()方法怒竿。
String a = "I am HaC";
Integer b = 100;
在編譯階段就把所有的字符串文字放到一個常量池中砍鸠,復(fù)用同一個(比如說上述的“I am HaC”),節(jié)省空間耕驰。
關(guān)于方法區(qū)和元空間的關(guān)系:
方法區(qū)是JVM規(guī)范概念爷辱,而永久代則是Hotspot虛擬機(jī)特有的概念,簡單點(diǎn)理解:方法區(qū)和堆內(nèi)存的永久代其實(shí)一個東西朦肘,但是方法區(qū)是包含了永久代饭弓。
只有 HotSpot 才有 “PermGen space”,而對于其他類型的虛擬機(jī)媒抠,如 JRockit(Oracle)弟断、J9(IBM) 并沒有“PermGen space”
7、元空間
1.8就把方法區(qū)改用元空間了趴生。類的元信息被存儲在元空間中阀趴。元空間沒有使用堆內(nèi)存昏翰,而是與堆不相連的本地內(nèi)存區(qū)域。所以刘急,理論上系統(tǒng)可以使用的內(nèi)存有多大棚菊,元空間就有多大,所以不會出現(xiàn)永久代存在時的內(nèi)存溢出問題叔汁。
可以通過 -XX:MetaspaceSize
和 -XX:MaxMetaspaceSize
來指定元空間的大小统求。
8、總結(jié):
參考: