【JAVA】【JVM篇】【JVM的組成】
來(lái)自二線(xiàn)的碼農(nóng)筆記冀瓦,用自己的理解總結(jié)知識(shí)點(diǎn),互相學(xué)習(xí)
1. JVM概念
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫(xiě)颅围,JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī),是通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的。
2. JVM作用
引入Java語(yǔ)言虛擬機(jī)后睛榄,Java語(yǔ)言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯。Java語(yǔ)言的可移植性正是建立在Java虛擬機(jī)的基礎(chǔ)上(不同系統(tǒng)安裝不同版本JVM)胯盯。任何平臺(tái)只要裝有針對(duì)于該平臺(tái)的Java虛擬機(jī)懈费,字節(jié)碼文件(.class)就可以在該平臺(tái)上運(yùn)行计露。這就是“一次編譯博脑,多次運(yùn)行”。
3. JVM(主要)組成部分
1. 程序計(jì)數(shù)器 : 記錄當(dāng)前線(xiàn)程所執(zhí)行到的字節(jié)碼行數(shù)
2. 虛擬機(jī)棧:存放方法運(yùn)行時(shí)候的棧幀
3. java堆:存儲(chǔ)對(duì)象實(shí)例,好比new出來(lái)的都在堆里
4. 本地方法棧: JVM調(diào)用本地方法,提供Native服務(wù)
5. 方法區(qū): 存儲(chǔ)運(yùn)行時(shí)候常量池票罐、已被虛擬機(jī)下載的類(lèi)信息叉趣、常量、靜態(tài)變量该押、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)疗杉。
4. JVM(主要)組成部分詳解
1.程序計(jì)數(shù)器
PC寄存器(程序計(jì)數(shù)器)用來(lái)存儲(chǔ)指向下一條指令的地址,也即將要執(zhí)行的指令代碼。CPU來(lái)回切換線(xiàn)程后能夠更好的知道接下里該執(zhí)行哪條指令, 由執(zhí)行引擎讀取下一條指令烟具。
代碼展示
public class PCRegisterTest {
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
}
}
然后將代碼進(jìn)行編譯成字節(jié)碼文件梢什,我們?cè)俅尾榭?,發(fā)現(xiàn)在字節(jié)碼的左邊有一個(gè)行號(hào)標(biāo)識(shí)朝聋,它其實(shí)就是指令地址嗡午,用于指向當(dāng)前執(zhí)行到哪里。
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: return
通過(guò)程序計(jì)數(shù)器我們就可以清楚的知道程序執(zhí)行到哪一步了.
那程序計(jì)數(shù)器存儲(chǔ)字節(jié)碼指令地址到底有什么用呢?
因?yàn)镃PU需要不停的切換各個(gè)線(xiàn)程冀痕,這時(shí)候切換回來(lái)以后荔睹,就得知道接著從哪開(kāi)始繼續(xù)執(zhí)行。
JVM的字節(jié)碼解釋器就需要通過(guò)改變程序計(jì)數(shù)器的值來(lái)明確下一條應(yīng)該執(zhí)行什么樣的字節(jié)碼指令言蛇。
程序計(jì)數(shù)器為什么被設(shè)定為私有的僻他?
我們都知道所謂的多線(xiàn)程在一個(gè)特定的時(shí)間段內(nèi)只會(huì)執(zhí)行其中某一個(gè)線(xiàn)程的方法,CPU會(huì)不停地做任務(wù)切換腊尚,這樣必然導(dǎo)致經(jīng)常中斷或恢復(fù)吨拗,如何保證分毫無(wú)差呢?為了能夠準(zhǔn)確地記錄各個(gè)線(xiàn)程正在執(zhí)行的當(dāng)前字節(jié)碼指令地址婿斥,最好的辦法丢胚。自然是為每一個(gè)線(xiàn)程都分配一個(gè)程序計(jì)數(shù)器,這樣一來(lái)各個(gè)線(xiàn)程之間便可以進(jìn)行獨(dú)立計(jì)算受扳,從而不會(huì)出現(xiàn)相互干擾的情況
2. 虛擬機(jī)棧
虛擬機(jī)棧的棧元素是棧幀携龟,當(dāng)有一個(gè)方法被調(diào)用時(shí),代表這個(gè)方法的棧幀入棧勘高;
當(dāng)這個(gè)方法返回時(shí)峡蟋,其棧幀出棧。因此华望,虛擬機(jī)棧中棧幀的入棧順序就是方法調(diào)用順序蕊蝗。虛擬機(jī)棧的主要作用反應(yīng)程序調(diào)用方法的順序【先進(jìn)后出原則】
何為棧幀?
棧幀(Stack Frame)是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的虛擬機(jī)棧(Virtual Machine Stack)的棧元素赖舟。每個(gè)方法被調(diào)用會(huì)生成一個(gè)新的棧幀進(jìn)棧蓬戚。棧幀存儲(chǔ)了方法的局部變量表,操作數(shù)棧宾抓,動(dòng)態(tài)連接和方法返回地址等信息子漩。第一個(gè)方法從調(diào)用開(kāi)始到執(zhí)行完成,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程石洗。
白話(huà)文談?wù)勏冗M(jìn)后出原則
學(xué)過(guò)數(shù)據(jù)結(jié)構(gòu)的小伙伴們應(yīng)該都知道棧的特點(diǎn)幢泼。那我們?cè)撛趺慈ダ斫饽兀?br> 拿APP頁(yè)面舉個(gè)例子, 比如說(shuō)你打開(kāi)美團(tuán)【APP首頁(yè)】(A)讲衫,這個(gè)時(shí)候我想打開(kāi)【外賣(mài)頁(yè)】(B)【點(diǎn)個(gè)外賣(mài)】(C)缕棵。這樣的話(huà)就是A調(diào)用B,B調(diào)用C。那我想關(guān)閉外賣(mài)頁(yè)招驴,我想找個(gè)娛樂(lè)場(chǎng)所放松一下心情篙程,那么我們就必須先返回外賣(mài)頁(yè),再返回到APP首頁(yè)别厘,再去找場(chǎng)所房午。這樣就是先關(guān)閉C再關(guān)閉B。C是最后進(jìn)去的卻被先關(guān)閉了丹允。 這樣去理解先進(jìn)后出原則郭厌。【建議大家百度一些先進(jìn)后出案例】
每個(gè)棧幀的存儲(chǔ)
- 局部變量表: 局部變量表也被稱(chēng)之為局部變量數(shù)組或本地變量表, 主要用于存儲(chǔ)方法參數(shù)和定義在方法體內(nèi)的局部變量雕蔽。局部變量表的大小問(wèn)題是在編譯時(shí)期就被確定折柠。
- 操作數(shù)棧: 每一個(gè)獨(dú)立的棧幀中除了包含局部變量表以外,還包含一個(gè)后進(jìn)先出(Last-In-First-Out)的操作數(shù)棧,也可以稱(chēng)之為表達(dá)式棧(Expression stack)。操作數(shù)棧,在方法執(zhí)行過(guò)程中,根據(jù)字節(jié)碼指令,往棧中寫(xiě)入數(shù)據(jù)或提取數(shù)據(jù)批狐,即入棧(push)/出棧(pop)扇售。
- *動(dòng)態(tài)連接:每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,Class文件的常量池中存有大量的符號(hào)引用嚣艇,字節(jié)碼中的方法調(diào)用指令就以常量池中方法的符號(hào)引用為參數(shù)承冰。這些符號(hào)引用一部分會(huì)在類(lèi)加載階段或者第一次使用的時(shí)候就轉(zhuǎn)化為直接引用(靜態(tài)方法,私有方法等)食零,這種轉(zhuǎn)化稱(chēng)為靜態(tài)解析困乒,另一部分將在每一次運(yùn)行期間轉(zhuǎn)化為直接引用,這部分稱(chēng)為動(dòng)態(tài)連接贰谣。由于篇幅有限這里不再繼續(xù)討論解析與分派的過(guò)程娜搂,這里只需要知道靜態(tài)解析與動(dòng)態(tài)連接的區(qū)別就好。
- 方法的返回值:執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令:傳遞給上層的方法調(diào)用者吱抚,是否有返回值和返回值類(lèi)型將根據(jù)遇到何種方法來(lái)返回指令決定百宇,這種退出的方法稱(chēng)為正常完成出口。如果存在異常,導(dǎo)致方法退出,是不會(huì)給上一級(jí)返回值的秘豹。無(wú)論哪一種方式退出都會(huì)返回方法的被調(diào)用的位置
3. 虛擬機(jī)堆
JVM內(nèi)存劃分為堆內(nèi)存和非堆內(nèi)存携御,堆內(nèi)存分為年輕代(Young Generation)、老年代(Old Generation)既绕,非堆內(nèi)存就一個(gè)永久代(Permanent Generation)啄刹。
年輕代又分為Eden和Survivor區(qū)。Survivor區(qū)由FromSpace和ToSpace組成岸更。Eden區(qū)占大容量鸵膏,Survivor兩個(gè)區(qū)占小容量,默認(rèn)比例是8:1:1怎炊。
堆內(nèi)存用途:存放的是對(duì)象,垃圾收集器就是收集這些對(duì)象,然后根據(jù)GC算法回收评肆。
非堆內(nèi)存用途:永久代债查,也稱(chēng)為方法區(qū),存儲(chǔ)程序運(yùn)行時(shí)長(zhǎng)期存活的對(duì)象瓜挽,比如類(lèi)的元數(shù)據(jù)盹廷、方法、常量久橙、屬性等俄占。
轉(zhuǎn)載文章
JAVA堆內(nèi)存管理是影響性能主要因素之一。
堆內(nèi)存溢出是JAVA項(xiàng)目非常常見(jiàn)的故障淆衷,在解決該問(wèn)題之前缸榄,必須先了解下JAVA堆內(nèi)存是怎么工作的。
先看下JAVA堆內(nèi)存是如何劃分的祝拯,如圖:
在JDK1.8版本廢棄了永久代甚带,替代的是元空間(MetaSpace),元空間與永久代上類(lèi)似佳头,都是方法區(qū)的實(shí)現(xiàn)鹰贵,他們最大區(qū)別是:元空間并不在JVM中,而是使用本地內(nèi)存康嘉。
元空間有注意有兩個(gè)參數(shù):
MetaspaceSize :初始化元空間大小碉输,控制發(fā)生GC閾值
MaxMetaspaceSize : 限制元空間大小上限,防止異常占用過(guò)多物理內(nèi)存
為什么移除永久代亭珍?
移除永久代原因:為融合HotSpot JVM與JRockit VM(新JVM技術(shù))而做出的改變腊瑟,因?yàn)镴Rockit沒(méi)有永久代。
有了元空間就不再會(huì)出現(xiàn)永久代OOM問(wèn)題了块蚌!
分代概念(每個(gè)區(qū)的作用)
新生成的對(duì)象首先放到年輕代Eden區(qū)闰非,當(dāng)Eden空間滿(mǎn)了,觸發(fā)Minor GC峭范,存活下來(lái)的對(duì)象移動(dòng)到Survivor0區(qū)财松,Survivor0區(qū)滿(mǎn)后觸發(fā)執(zhí)行Minor GC,Survivor0區(qū)存活對(duì)象移動(dòng)到Suvivor1區(qū)纱控,這樣保證了一段時(shí)間內(nèi)總有一個(gè)survivor區(qū)為空辆毡。經(jīng)過(guò)多次Minor GC仍然存活的對(duì)象移動(dòng)到老年代。
老年代存儲(chǔ)長(zhǎng)期存活的對(duì)象甜害,占滿(mǎn)時(shí)會(huì)觸發(fā)Major GC=Full GC舶掖,GC期間會(huì)停止所有線(xiàn)程等待GC完成,所以對(duì)響應(yīng)要求高的應(yīng)用盡量減少發(fā)生Major GC尔店,避免響應(yīng)超時(shí)眨攘。
Minor GC : 清理年輕代
Major GC : 清理老年代
Full GC : 清理整個(gè)堆空間主慰,包括年輕代和永久代
所有GC都會(huì)停止應(yīng)用所有線(xiàn)程。
為什么分代(年輕代和老年代)鲫售?
將對(duì)象根據(jù)存活概率進(jìn)行分類(lèi)共螺,對(duì)存活時(shí)間長(zhǎng)的對(duì)象,放到固定區(qū)情竹,從而減少掃描垃圾時(shí)間及GC頻率藐不。針對(duì)分類(lèi)進(jìn)行不同的垃圾回收算法,對(duì)算法揚(yáng)長(zhǎng)避短秦效。
為什么會(huì)堆內(nèi)存溢出雏蛮?
在年輕代中經(jīng)過(guò)GC后還存活的對(duì)象會(huì)被復(fù)制到老年代中。當(dāng)老年代空間不足時(shí)阱州,JVM會(huì)對(duì)老年代進(jìn)行完全的垃圾回收(Full GC)挑秉。如果GC后,還是無(wú)法存放從Survivor區(qū)復(fù)制過(guò)來(lái)的對(duì)象贡耽,就會(huì)出現(xiàn)OOM(Out of Memory)衷模。
OOM(Out of Memory)異常常見(jiàn)有以下幾個(gè)原因:
1)老年代內(nèi)存不足:java.lang.OutOfMemoryError:Javaheapspace
2)永久代內(nèi)存不足:java.lang.OutOfMemoryError:PermGenspace
3)代碼bug,占用內(nèi)存無(wú)法及時(shí)回收蒲赂。
4.本地方法棧
對(duì)于一個(gè)運(yùn)行中的Java程序而言阱冶,它還可能會(huì)用到一些跟本地方法相關(guān)的數(shù)據(jù)區(qū)。通俗的說(shuō)就是程序在執(zhí)行的過(guò)程中會(huì)使用到一些非Java語(yǔ)言實(shí)現(xiàn)的方法(例如:調(diào)用本地打印機(jī)的方法)滥嘴,這個(gè)時(shí)候就會(huì)用到本地方法棧木蹬。
當(dāng)一個(gè)線(xiàn)程調(diào)用JAVA方法和本地方法時(shí)的棧圖如下
當(dāng)線(xiàn)程調(diào)用Java方法時(shí),虛擬機(jī)會(huì)創(chuàng)建一個(gè)新的棧幀并壓入Java棧若皱。然而當(dāng)它調(diào)用的是本地方法時(shí)镊叁,虛擬機(jī)會(huì)保持Java棧不變,不再在線(xiàn)程的Java棧中壓入新的幀走触,虛擬機(jī)只是簡(jiǎn)單地動(dòng)態(tài)連接并直接調(diào)用指定的本地方法晦譬。
5.方法區(qū)
方法區(qū)與Java堆一樣,是各個(gè)線(xiàn)程共享的內(nèi)存區(qū)域互广,它用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息敛腌、常量、靜態(tài)變量惫皱、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)像樊。
方法區(qū)與堆一樣,是各個(gè)線(xiàn)程共享的內(nèi)存區(qū)域旅敷。方法區(qū)在 JVM 啟動(dòng)的時(shí)候被創(chuàng)建生棍,并且它的實(shí)際的物理內(nèi)存空間中和 Java 堆區(qū)一樣都可以是不連續(xù)的∠彼空間大小可選擇固定或者可擴(kuò)展涂滴。
方法區(qū)的大小決定了系統(tǒng)可以保存多少個(gè)類(lèi)友酱,如果系統(tǒng)定義了太多類(lèi),導(dǎo)致方法區(qū)溢出氢妈,虛擬機(jī)同樣會(huì)拋出內(nèi)存溢出錯(cuò)誤:java.lang.OutOfMemoryError(PermGen space)或者 java.lang.OutOfMemoryError (Metaspace)
運(yùn)行時(shí)常量池
是方法區(qū)的一部分粹污。存儲(chǔ):數(shù)量值段多、字符串值首量、類(lèi)引用、字段引用进苍、方法引用加缘。
常量池表示 Class 文件的一部分,用于存放編譯器生成的各種字面量與符號(hào)引用觉啊,這部分內(nèi)容將在類(lèi)加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中拣宏。運(yùn)行時(shí)常量池在加載類(lèi)和接口道虛擬機(jī)后,就會(huì)常見(jiàn)對(duì)應(yīng)的運(yùn)行時(shí)常量池杠人。
JVM 為每個(gè)已加載的類(lèi)型 (類(lèi)或接口) 都維護(hù)一個(gè)常量池勋乾。池中的數(shù)據(jù)項(xiàng)像數(shù)組項(xiàng)一樣,是通過(guò)索引訪(fǎng)問(wèn)的嗡善。
運(yùn)行時(shí)常量池中包含多種不同的常量辑莫,包括編譯器就已經(jīng)明確的數(shù)值字面量,也包括到運(yùn)行期解析后才能夠獲得的方法或者字段引用罩引。此時(shí)不再是常量池中的符號(hào)地址了各吨,這里換為真實(shí)地址。運(yùn)行時(shí)常量池袁铐,相當(dāng)于 Class 文件常量池的另一重要特征:具備動(dòng)態(tài)性(String.intern())
當(dāng)創(chuàng)建類(lèi)或接口的運(yùn)行時(shí)常量池時(shí)揭蜒,如果構(gòu)造運(yùn)行時(shí)常量池所需的內(nèi)存空間超過(guò)了方法區(qū)所能提供的最大值,則 JVM 會(huì)拋 OutOfMemoryError 異常剔桨。
部分概念比較模糊屉更,可自行百度