1.程序計(jì)數(shù)器概念程序計(jì)數(shù)器也叫作PC寄存器,是一塊很小的內(nèi)存區(qū)域,可以看做是當(dāng)前線程執(zhí)行的字節(jié)碼的行號(hào)指示器。字節(jié)碼的解釋工作就是通過(guò)改變程序計(jì)數(shù)器里面的值來(lái)獲得下一條需要執(zhí)行字節(jié)碼的指令。 特點(diǎn)- Pc寄存器表現(xiàn)為一塊內(nèi)存,功能是存放偽指令,確切的說(shuō)是存放的將要執(zhí)行指令的地址。
- 當(dāng)虛擬機(jī)正在執(zhí)行的是一個(gè)native方法時(shí),JVM的PC寄存器存儲(chǔ)的值是undefined。
- 程序計(jì)數(shù)器是線程私有的,它的生命周期和線程一樣,每個(gè)線程只有一個(gè)。這也是為了保證多線程下,線程切換后能恢復(fù)到正確的執(zhí)行位置,所以每個(gè)線程需要獨(dú)立的程序計(jì)數(shù)器,相互隔離互不影響。
- 此區(qū)域是唯一一個(gè)沒(méi)有OOM情況的區(qū)域。
圖例2.虛擬機(jī)棧概念JAVA虛擬機(jī)棧的生命周期和線程相同,他也是線程私有的,每一個(gè)線程有自己獨(dú)立的虛擬機(jī)棧。他用來(lái)存儲(chǔ)棧幀,程序運(yùn)行時(shí),每一個(gè)方法被調(diào)用執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用來(lái)存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)方法從調(diào)用到執(zhí)行完就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)中從入棧到出棧的過(guò)程。 圖例演示棧幀棧幀是支持虛擬機(jī)方法調(diào)用和執(zhí)行的數(shù)據(jù)結(jié)構(gòu)。棧幀中存儲(chǔ)了方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)連接和方法返回地址等信息。每一個(gè)方法的調(diào)用和執(zhí)行完成都對(duì)應(yīng)著一個(gè)棧幀從虛擬機(jī)棧中入棧到出棧的過(guò)程。 設(shè)置虛擬機(jī)棧的大小-Xss為JVM啟動(dòng)時(shí)的每個(gè)線程分配的內(nèi)存大小,也就是可以設(shè)置線程棧的大小。 -Xss1m # 單位為MB-Xss1024k #單位為KB-Xss1048576 #字節(jié)大小
局部變量表局部變量表是一組變量值存儲(chǔ)空間,用于存放方法的參數(shù)和方法內(nèi)定義的局部變量。 操作數(shù)棧操作數(shù)棧是一個(gè)后入先出棧(LIFO)。隨著方法執(zhí)行和字節(jié)碼指令的執(zhí)行,會(huì)從局部變量表或者對(duì)象實(shí)例的字段中復(fù)制常量或者變量寫到操作數(shù)棧,再隨著計(jì)算的進(jìn)行會(huì)將棧中的元素出棧到局部變量表或者返回給方法調(diào)用者。 動(dòng)態(tài)鏈接java虛擬機(jī)中,每一個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧所屬方法的符號(hào)引用,持有這個(gè)引用的目的為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)鏈接。 動(dòng)態(tài)鏈接的作用:將符號(hào)引用轉(zhuǎn)換為直接引用。 方法返回地址方法返回地址存放調(diào)用該方法的PC寄存器的值。一個(gè)方法的結(jié)束,有兩種方式:正常地執(zhí)行完成,出現(xiàn)未處理的異 常非正常的退出。無(wú)論通過(guò)哪種方式退出,在方法退出后都返回到該方法被調(diào)用的位置。方法正常退出時(shí),調(diào)用者 的PC計(jì)數(shù)器的值作為返回地址,即調(diào)用該方法的指令的下一條指令的地址。而通過(guò)異常退出的,返回地址是要通過(guò) 異常表來(lái)確定,棧幀中一般不會(huì)保存這部分信息。 無(wú)論方法是否正常完成,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)進(jìn)行。 3.本地方法棧概念本地方法棧則是為虛擬機(jī)使用到的本地(Native) 方法服務(wù),而虛擬機(jī)棧是為使用到的java方法服務(wù)。 關(guān)于native方法native關(guān)鍵字修飾的Java方法是一個(gè)原生態(tài)方法,方法對(duì)應(yīng)的實(shí)現(xiàn)Java作用范圍達(dá)不到,而是在用其他編程語(yǔ)言(如C和C++)文件中實(shí)現(xiàn)。Java語(yǔ)言本身不能直接對(duì)操作系統(tǒng)底層進(jìn)行訪問(wèn)和操作,但可以通過(guò)JNI接口調(diào)用其他編程語(yǔ)言來(lái)實(shí)現(xiàn)對(duì)操作系統(tǒng)底層的訪問(wèn)。 native方法在異地實(shí)現(xiàn),類似抽象方法,不能有方法體,要以分號(hào)結(jié)束。例如: 本地方法棧特點(diǎn)- 本地方法棧加載nativef方法,是為了填補(bǔ)java不方便實(shí)現(xiàn)的場(chǎng)景產(chǎn)生的。
- 虛擬機(jī)棧為為虛擬機(jī)執(zhí)行java服務(wù),而本地方法棧為了執(zhí)行虛擬機(jī)所使用到的native服務(wù)
- 本地方法棧也是線程私有的,和線程的生命周期是一致的,每個(gè)線程都有一個(gè)本地方法棧。
4.堆4.1 堆的總括4.1.1 概念Java堆(Java Heap) 是虛擬機(jī)所管理的內(nèi)存中最大的一塊。 Java堆是被所 有線程共享的一塊內(nèi)存區(qū)域, 在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。 此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例, Java 世界里“幾乎”所有的對(duì)象實(shí)例都在這里分配內(nèi)存。 4.1.2 特點(diǎn)- 堆是Java虛擬機(jī)所管理內(nèi)存中最大的一塊區(qū)域。
- 堆是線程共享的。
- 堆在虛擬機(jī)啟動(dòng)的時(shí)候創(chuàng)建。
- 堆存在的目的就是存放對(duì)象實(shí)例。
- 堆是垃圾回收管理的主要區(qū)域。因此堆又被稱作為GC堆,JAVA堆還可以細(xì)分為新生代,老年代,永久代(jdk8以后就取消了),其中新生代又分為Eden空間、From survivor、To survivor。
- 堆在計(jì)算機(jī)物理上存儲(chǔ)是不連續(xù)的,但是邏輯上是連續(xù)的,它的大小可以調(diào)節(jié)(-Xmx,-Xms控制)。
- 方法結(jié)束后,堆對(duì)象不會(huì)馬上的移除,僅僅在垃圾回收的時(shí)候才會(huì)移除。
- 如果堆中沒(méi)有足夠的內(nèi)存完成對(duì)實(shí)例的分配,且堆的空間無(wú)法再擴(kuò)展時(shí),那么將會(huì)報(bào)出OOM異常。
4.1.3 設(shè)置堆內(nèi)存大小我們可以通過(guò)-Xms來(lái)設(shè)置最小堆內(nèi)存,通過(guò)-Xmx設(shè)置最大堆內(nèi)存。 以上是設(shè)置了:-Xms5m -Xmx20m 這里可以看出打印出來(lái)的Xmx值18m和設(shè)置的值20m之間是有差異的,total Memory和最大的內(nèi)存之間也還是存在比較明顯的差異,就是說(shuō)JVM一般會(huì)盡量保持內(nèi)存在一個(gè)盡可能底的層面,而非貪婪做法按照最大的內(nèi)存來(lái)進(jìn)行分配。 另外,當(dāng)我們申請(qǐng)分配內(nèi)存10m時(shí),我們會(huì)發(fā)現(xiàn)free Memory和total Memory都上升了,可以看出JVM在內(nèi)存分配時(shí)是動(dòng)態(tài)分配的。 4.1.4堆的分類JAVA將虛擬機(jī)堆分為三個(gè)部分: - 新生代 (又分為伊甸園區(qū),幸存者區(qū)s0和幸存者區(qū)s1)
- 老年代
- 永久代(JDK1.8后沒(méi)有了,被本地內(nèi)存的元空間取代了)
圖例如下: 4.2 新生代和老年代4.2.1 對(duì)象存儲(chǔ)- 新生代存放剛創(chuàng)建的實(shí)例對(duì)象,內(nèi)存比較小,垃圾回收比較頻繁。新生代又分為Eden區(qū),survivor To區(qū)S0和survivor From區(qū)S1,其中S0區(qū)和S1區(qū)并不是固定的from及to的區(qū)域,由對(duì)象轉(zhuǎn)移的方向決定的,假設(shè)對(duì)象從S1轉(zhuǎn)移到S0,那么S1便是survivor From,S0是survivor To。
- 老年代主要存放一些生命周期比較長(zhǎng)的對(duì)象,經(jīng)過(guò)在新生代幾次的回收依舊沒(méi)有清除掉,那這部分實(shí)例便會(huì)轉(zhuǎn)移到老年代。老年代的垃圾回收相對(duì)來(lái)講沒(méi)有那么頻繁。
4.2.2 配置新生代和老年代的堆中占比默認(rèn)情況下-XX:NewRatio=2,表示新生代:老年代 = 1:2,新生代占整個(gè)堆空間的1/3 案例:假設(shè)我們將-XX:NewRatio修改為等于4,那么則表示新生代:老年代 = 1:4,那么新生代占整個(gè)堆空間的1/5 除了我們可以配置新生代和老年代的比例之外,我們還可以配置eden和S0和S1在新生代中的占比情況,默認(rèn)情況下-XX:SurvivorRatio = 8,表示Eden:S0:S1=8:1:1,這表示Eden占整個(gè)新生代的8/10,而兩個(gè)survivor區(qū)域分別占了1/10,另外,需要補(bǔ)充一點(diǎn),由于JVM在運(yùn)行時(shí),每次都只會(huì)使用Eden區(qū)和一塊survivor區(qū)進(jìn)行服務(wù),因此總是會(huì)有一個(gè)survivor區(qū)域是空閑著的,所以新生代的最高使用也只能達(dá)到9/10。 4.3 對(duì)象分配過(guò)程- new對(duì)象時(shí)首先會(huì)將對(duì)象放在eden區(qū),該區(qū)大小有內(nèi)存限制。
- 當(dāng)eden區(qū)的數(shù)據(jù)滿了之后,程序還需要?jiǎng)?chuàng)建對(duì)象,會(huì)觸發(fā)垃圾回收,將那些不再被引用的對(duì)象給銷毀掉。
- 剩余沒(méi)被回收掉的對(duì)象會(huì)被轉(zhuǎn)移到S0區(qū),而程序新創(chuàng)建的對(duì)象又會(huì)繼續(xù)寫入Eden區(qū)。
- 當(dāng)再次發(fā)生垃圾回收時(shí),如果S0中還存在未被銷毀的對(duì)象,那么這部分剩余的對(duì)象會(huì)被轉(zhuǎn)移到S1中。
- 之后每次經(jīng)歷垃圾回收,存在S0或者S1中未被銷毀的對(duì)象總會(huì)相互轉(zhuǎn)移過(guò)去。
- 當(dāng)這種轉(zhuǎn)移達(dá)到15次上限后,那么這部分對(duì)象將會(huì)被轉(zhuǎn)移到老年區(qū)。當(dāng)然這個(gè)閾值并不是固定15,可以通過(guò)調(diào)節(jié)參數(shù) -XX:MaxTenuringThreshold=N來(lái)控制閾值。
- 當(dāng)養(yǎng)老區(qū)的內(nèi)存也不足時(shí),會(huì)觸發(fā)GC進(jìn)行養(yǎng)老區(qū)的垃圾回收。
- 如果養(yǎng)老區(qū)進(jìn)行了GC垃圾回收后還是沒(méi)有辦法保存新創(chuàng)建的對(duì)象,那么將會(huì)報(bào)OOM異常。
4.4 堆GCJava中的堆是虛擬機(jī)中GC收集垃圾的主要區(qū)域。GC分為兩種,一種是部分收集(Partial GC),一種是整堆收集(Full GC). 部分收集 - 新生代收集(Minor GC/Yong GC):只是新生代的垃圾回收。
- 老年代收集(Major GC/Old GC):只是老年代的垃圾回收。
- 混合收集(Mixed) :收集整個(gè)新生代和老年的垃圾。(G1 GC會(huì)混合回收, region區(qū)域回收)
整堆收集(Full GC):收集整個(gè)java堆和方法區(qū)的垃圾收集器 年輕代GC觸發(fā)條件 - 當(dāng)年輕代內(nèi)存不足時(shí),會(huì)觸發(fā)Minor GC,這里的內(nèi)存不足指的是Eden區(qū)的內(nèi)存不足,Survivor區(qū)不會(huì)。
- Minor GC 會(huì)暫停其他用戶的線程,等到垃圾回收結(jié)束,用戶的線程才恢復(fù)。
老年代GC觸發(fā)條件 - 老年代空間不足時(shí),會(huì)嘗試觸發(fā)Minor Gc,如果空間還是不足,則會(huì)觸發(fā)Major GC
- 如果Major GC結(jié)束后,空間還是不足,會(huì)報(bào)OOM異常。
- Major GC的速度比Minor GC慢10倍以上。
Full GC觸發(fā)條件 - 程序調(diào)用System.gc(),會(huì)觸發(fā)Full GC,但不會(huì)立即去執(zhí)行。
- 老年代空間不足。
- 方法區(qū)空間不足。
- 通過(guò)Minor GC后仍能進(jìn)入老年代的對(duì)象所占空間大于老年代剩余可用空間。
5.元空間JDK1.8后為什么廢除永久代,引入元空間- 在之前的永久代中,它是堆的一部分,主要是在存儲(chǔ)類的元數(shù)據(jù)、靜態(tài)變量、常量等,這些數(shù)據(jù)的大小也不太容易控制和計(jì)算,開(kāi)發(fā)人員對(duì)永久代進(jìn)行調(diào)優(yōu)會(huì)有很多的難度。永久代會(huì)對(duì)GC帶來(lái)不必要的復(fù)雜度,回收效率偏低。
- 而用元空間替代永久代,這樣的話可以很好的解決這個(gè)問(wèn)題,因?yàn)樵臻g是放在本地內(nèi)存上的,簡(jiǎn)而言之,只要你服務(wù)器內(nèi)存還有,元空間基本就不會(huì)發(fā)生內(nèi)存溢出等問(wèn)題。
廢除永久代的好處- 由于類的元數(shù)據(jù)分配在本地內(nèi)存上,這樣就說(shuō)元空間的最大分配內(nèi)存就是服務(wù)器系統(tǒng)剩余可用內(nèi)存,不會(huì)遇到永久代時(shí)存在的內(nèi)存溢出問(wèn)題。
- 將運(yùn)行時(shí)常量池從永久代中分離出來(lái),與類的元數(shù)據(jù)分開(kāi),提高了元數(shù)據(jù)的獨(dú)立性。
- 將元數(shù)據(jù)從永久代剝離出來(lái)放到元空間,可以提升對(duì)元數(shù)據(jù)的管理,同時(shí)也提升GC效率。
元空間相關(guān)參數(shù)- -XX:MetaspaceSize,初始空間大小,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載,同時(shí)GC會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過(guò)MaxMetaspaceSize時(shí),適當(dāng)提高該值。
- -XX:MaxMetaspaceSize,最大空間,默認(rèn)是沒(méi)有限制的。如果沒(méi)有使用該參數(shù)來(lái)設(shè)置類的元數(shù)據(jù)的大小,其最大可利用空間是整個(gè)系統(tǒng)內(nèi)存的可用空間。JVM也可以增加本地內(nèi)存空間來(lái)滿足類元數(shù)據(jù)信息的存儲(chǔ)。但是如果沒(méi)有設(shè)置最大值,則可能存在bug導(dǎo)致Metaspace的空間在不停的擴(kuò)展,會(huì)導(dǎo)致機(jī)器的內(nèi)存不足;進(jìn)而可能出現(xiàn)swap內(nèi)存被耗盡;最終導(dǎo)致進(jìn)程直接被系統(tǒng)直接kill掉。如果設(shè)置了該參數(shù),當(dāng)Metaspace剩余空間不足,會(huì)拋出:java.lang.OutOfMemoryError: Metaspace space。
- -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導(dǎo)致的垃圾收集。
- -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導(dǎo)致的垃圾收集。
6.方法區(qū)6.1方法區(qū)的理解概念: 元空間、永久代是方法區(qū)具體的落地實(shí)現(xiàn)。方法區(qū)看作是一塊獨(dú)立于Java堆的內(nèi)存空間,它主要是用來(lái)存儲(chǔ)所加載 的類信息的,方法區(qū)是線程共享的。 特點(diǎn): - 方法區(qū)與堆一樣是各個(gè)線程共享的內(nèi)存區(qū)域。
- 方法區(qū)在JVM啟動(dòng)的時(shí)候就會(huì)被創(chuàng)建,并且它實(shí)際的物理內(nèi)存空間和Java堆一樣可以是不連續(xù)的。
- 方法區(qū)的大小跟堆一樣,可以選擇固定的大小或者動(dòng)態(tài)變化。
- 方法區(qū)的大小決定了系統(tǒng)可以保存多少個(gè)類,如果系統(tǒng)定義了太多的類,導(dǎo)致方法區(qū)溢出,虛擬機(jī)仍然會(huì)報(bào)OOM異常。
- 關(guān)閉虛擬機(jī)就會(huì)釋放方法區(qū)區(qū)域。
6.2 方法區(qū)結(jié)構(gòu)類加載器將Class文件加載到內(nèi)存以后,將類的信息存儲(chǔ)到方法區(qū)中。 方法區(qū)中存儲(chǔ)的內(nèi)容: - 類型信息(域信息、方法信息)
- 運(yùn)行時(shí)常量池
類型信息 - 這個(gè)類型的完整有效名稱(全名 = 包名.類名)
- 這個(gè)類型直接父類的完整有效名(對(duì)于 interface或是java.lang. Object,都沒(méi)有父類)
- 這個(gè)類型的修飾符( public, abstract,final的某個(gè)子集)
- 這個(gè)類型直接接口的一個(gè)有序列表
域信息 域信息,即為類的屬性,成員變量 JVM必須在方法區(qū)中保存類所有的成員變量相關(guān)信息及聲明順序。 域的相關(guān)信息包括:域名稱、域類型、域修飾符(pυblic、private、protected、static、final、volatile、transient的 某個(gè)子集) 方法信息 - 方法名稱方法的返回類型(或void)
- 方法參數(shù)的數(shù)量和類型(按順序)
- 方法的修飾符public、private、protected、static、final、synchronized、native,、abstract的一個(gè)子集
- 方法的字節(jié)碼bytecodes、操作數(shù)棧、局部變量表及大?。?abstract和native方法除外)
- 異常表( abstract和 native方法除外)。每個(gè)異常處理的開(kāi)始位置、結(jié)束位置、代碼處理在程序計(jì)數(shù)器中的偏
移地址、被捕獲的異常類的常量池索引
6.3 方法區(qū)設(shè)置方法區(qū)的大小不必是固定的,可以根據(jù)應(yīng)用的需要?jiǎng)討B(tài)調(diào)整 - jdk7及之前通過(guò)-xx:Permsize來(lái)設(shè)置永久代初始分配空間。-XX:MaxPermsize來(lái)設(shè)定永久代最大可分配空間。64位的機(jī)器默認(rèn)是82M。當(dāng)JVM加載的類信息容量超過(guò)了這個(gè)值,會(huì)報(bào)OOM異常:PermGen space。
- jdk8及以后元數(shù)據(jù)區(qū)大小可以使用參數(shù) -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定。但是元數(shù)據(jù)區(qū)的 -XX:MaxMetaspaceSize默認(rèn)是-1即沒(méi)有限制,不設(shè)置可以使用系統(tǒng)剩余所有內(nèi)存。如果元數(shù)據(jù)區(qū)發(fā)生溢出,虛擬機(jī)一樣會(huì)拋出異常OutOfMemoryError:Metaspace
7.運(yùn)行時(shí)常量池字節(jié)碼文件中,內(nèi)部包含了常量池。 方法區(qū)中,內(nèi)部包含了運(yùn)行時(shí)常量池。 常量池:存放了編譯期間產(chǎn)生的各種字面量和符號(hào)引用。 運(yùn)行時(shí)常量池:是常量池表在運(yùn)行時(shí)的一種表現(xiàn)形式。 編譯后的字節(jié)碼文件中包含了類型信息、域信息、方法信息等。通過(guò)ClassLoader將字節(jié)碼文件的常量池中的信息加載到內(nèi)存中,存儲(chǔ)在了方法區(qū)的運(yùn)行時(shí)常量池中。 常量池,可以看做是一張表,虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量等類型。
|