天天看點

詳細了解JVM運作時記憶體

1.程式計數器

概念

程式計數器也叫作PC寄存器,是一塊很小的記憶體區域,可以看做是目前線程執行的位元組碼的行号訓示器。位元組碼的解釋工作就是通過改變程式計數器裡面的值來獲得下一條需要執行位元組碼的指令。

特點

  • Pc寄存器表現為一塊記憶體,功能是存放僞指令,确切的說是存放的将要執行指令的位址。
  • 當虛拟機正在執行的是一個native方法時,JVM的PC寄存器存儲的值是undefined。
  • 程式計數器是線程私有的,它的生命周期和線程一樣,每個線程隻有一個。這也是為了保證多線程下,線程切換後能恢複到正确的執行位置,是以每個線程需要獨立的程式計數器,互相隔離互不影響。
  • 此區域是唯一一個沒有OOM情況的區域。

圖例

詳細了解JVM運作時記憶體

2.虛拟機棧

概念

JAVA虛拟機棧的生命周期和線程相同,他也是線程私有的,每一個線程有自己獨立的虛拟機棧。他用來存儲棧幀,程式運作時,每一個方法被調用執行時都會建立一個棧幀,用來存儲局部變量表、操作數棧、動态連結、方法出口等資訊。每一個方法從調用到執行完就對應着一個棧幀在虛拟機中從入棧到出棧的過程。

圖例示範

詳細了解JVM運作時記憶體

棧幀

棧幀是支援虛拟機方法調用和執行的資料結構。棧幀中存儲了方法的局部變量表、操作數棧、動态連接配接和方法傳回位址等資訊。每一個方法的調用和執行完成都對應着一個棧幀從虛拟機棧中入棧到出棧的過程。

詳細了解JVM運作時記憶體

設定虛拟機棧的大小

-Xss為JVM啟動時的每個線程配置設定的記憶體大小,也就是可以設定線程棧的大小。

-Xss1m     # 機關為MB
-Xss1024k  #機關為KB
-Xss1048576  #位元組大小      

局部變量表

局部變量表是一組變量值存儲空間,用于存放方法的參數和方法内定義的局部變量。

詳細了解JVM運作時記憶體

操作數棧

操作數棧是一個後入先出棧(LIFO)。随着方法執行和位元組碼指令的執行,會從局部變量表或者對象執行個體的字段中複制常量或者變量寫到操作數棧,再随着計算的進行會将棧中的元素出棧到局部變量表或者傳回給方法調用者。

動态連結

java虛拟機中,每一個棧幀都包含一個指向運作時常量池中該棧所屬方法的符号引用,持有這個引用的目的為了支援方法調用過程中的動态連結。 動态連結的作用:将符号引用轉換為直接引用。

方法傳回位址

方法傳回位址存放調用該方法的PC寄存器的值。一個方法的結束,有兩種方式:正常地執行完成,出現未處理的異

常非正常的退出。無論通過哪種方式退出,在方法退出後都傳回到該方法被調用的位置。方法正常退出時,調用者

的PC計數器的值作為傳回位址,即調用該方法的指令的下一條指令的位址。而通過異常退出的,傳回位址是要通過

異常表來确定,棧幀中一般不會儲存這部分資訊。

無論方法是否正常完成,都需要傳回到方法被調用的位置,程式才能繼續進行。

3.本地方法棧

概念

本地方法棧則是為虛拟機使用到的本地(Native) 方法服務,而虛拟機棧是為使用到的java方法服務。

關于native方法

native關鍵字修飾的Java方法是一個原生态方法,方法對應的實作Java作用範圍達不到,而是在用其他程式設計語言(如C和C++)檔案中實作。Java語言本身不能直接對作業系統底層進行通路和操作,但可以通過JNI接口調用其他程式設計語言來實作對作業系統底層的通路。 native方法在異地實作,類似抽象方法,不能有方法體,要以分号結束。例如:

詳細了解JVM運作時記憶體

本地方法棧特點

  • 本地方法棧加載nativef方法,是為了填補java不友善實作的場景産生的。
  • 虛拟機棧為為虛拟機執行java服務,而本地方法棧為了執行虛拟機所使用到的native服務
  • 本地方法棧也是線程私有的,和線程的生命周期是一緻的,每個線程都有一個本地方法棧。

4.堆

4.1 堆的總括

4.1.1 概念

Java堆(Java Heap) 是虛拟機所管理的記憶體中最大的一塊。 Java堆是被所 有線程共享的一塊記憶體區域, 在虛拟機啟動時建立。 此記憶體區域的唯一目的就是存放對象執行個體, Java 世界裡“幾乎”所有的對象執行個體都在這裡配置設定記憶體。

4.1.2 特點
  • 堆是Java虛拟機所管理記憶體中最大的一塊區域。
  • 堆是線程共享的。
  • 堆在虛拟機啟動的時候建立。
  • 堆存在的目的就是存放對象執行個體。
  • 堆是垃圾回收管理的主要區域。是以堆又被稱作為GC堆,JAVA堆還可以細分為新生代,老年代,永久代(jdk8以後就取消了),其中新生代又分為Eden空間、From survivor、To survivor。
  • 堆在計算機實體上存儲是不連續的,但是邏輯上是連續的,它的大小可以調節(-Xmx,-Xms控制)。
  • 方法結束後,堆對象不會馬上的移除,僅僅在垃圾回收的時候才會移除。
  • 如果堆中沒有足夠的記憶體完成對執行個體的配置設定,且堆的空間無法再擴充時,那麼将會報出OOM異常。
4.1.3 設定堆記憶體大小

我們可以通過-Xms來設定最小堆記憶體,通過-Xmx設定最大堆記憶體。

詳細了解JVM運作時記憶體

以上是設定了:-Xms5m -Xmx20m

這裡可以看出列印出來的Xmx值18m和設定的值20m之間是有差異的,total Memory和最大的記憶體之間也還是存在比較明顯的差異,就是說JVM一般會盡量保持記憶體在一個盡可能底的層面,而非貪婪做法按照最大的記憶體來進行配置設定。

另外,當我們申請配置設定記憶體10m時,我們會發現free Memory和total Memory都上升了,可以看出JVM在記憶體配置設定時是動态配置設定的。

4.1.4堆的分類

JAVA将虛拟機堆分為三個部分:

  • 新生代 (又分為伊甸園區,幸存者區s0和幸存者區s1)
  • 老年代
  • 永久代(JDK1.8後沒有了,被本地記憶體的元空間取代了)

圖例如下:

詳細了解JVM運作時記憶體
詳細了解JVM運作時記憶體

4.2 新生代和老年代

4.2.1 對象存儲
  • 新生代存放剛建立的執行個體對象,記憶體比較小,垃圾回收比較頻繁。新生代又分為Eden區,survivor To區S0和survivor From區S1,其中S0區和S1區并不是固定的from及to的區域,由對象轉移的方向決定的,假設對象從S1轉移到S0,那麼S1便是survivor From,S0是survivor To。
  • 老年代主要存放一些生命周期比較長的對象,經過在新生代幾次的回收依舊沒有清除掉,那這部分執行個體便會轉移到老年代。老年代的垃圾回收相對來講沒有那麼頻繁。
4.2.2 配置新生代和老年代的堆中占比

預設情況下-XX:NewRatio=2,表示新生代:老年代 = 1:2,新生代占整個堆空間的1/3

案例:假設我們将-XX:NewRatio修改為等于4,那麼則表示新生代:老年代 = 1:4,那麼新生代占整個堆空間的1/5

除了我們可以配置新生代和老年代的比例之外,我們還可以配置eden和S0和S1在新生代中的占比情況,預設情況下-XX:SurvivorRatio = 8,表示Eden:S0:S1=8:1:1,這表示Eden占整個新生代的8/10,而兩個survivor區域分别占了1/10,另外,需要補充一點,由于JVM在運作時,每次都隻會使用Eden區和一塊survivor區進行服務,是以總是會有一個survivor區域是空閑着的,是以新生代的最高使用也隻能達到9/10。

4.3 對象配置設定過程

  • new對象時首先會将對象放在eden區,該區大小有記憶體限制。
  • 當eden區的資料滿了之後,程式還需要建立對象,會觸發垃圾回收,将那些不再被引用的對象給銷毀掉。
  • 剩餘沒被回收掉的對象會被轉移到S0區,而程式新建立的對象又會繼續寫入Eden區。
  • 當再次發生垃圾回收時,如果S0中還存在未被銷毀的對象,那麼這部分剩餘的對象會被轉移到S1中。
  • 之後每次經曆垃圾回收,存在S0或者S1中未被銷毀的對象總會互相轉移過去。
  • 當這種轉移達到15次上限後,那麼這部分對象将會被轉移到老年區。當然這個門檻值并不是固定15,可以通過調節參數 -XX:MaxTenuringThreshold=N來控制門檻值。
  • 當養老區的記憶體也不足時,會觸發GC進行養老區的垃圾回收。
  • 如果養老區進行了GC垃圾回收後還是沒有辦法儲存新建立的對象,那麼将會報OOM異常。

4.4 堆GC

Java中的堆是虛拟機中GC收集垃圾的主要區域。GC分為兩種,一種是部分收集(Partial GC),一種是整堆收集(Full GC).

部分收集

  • 新生代收集(Minor GC/Yong GC):隻是新生代的垃圾回收。
  • 老年代收集(Major GC/Old GC):隻是老年代的垃圾回收。
  • 混合收集(Mixed) :收集整個新生代和老年的垃圾。(G1 GC會混合回收, region區域回收)

**整堆收集(Full GC):**收集整個java堆和方法區的垃圾收集器

年輕代GC觸發條件

  • 當年輕代記憶體不足時,會觸發Minor GC,這裡的記憶體不足指的是Eden區的記憶體不足,Survivor區不會。
  • Minor GC 會暫停其他使用者的線程,等到垃圾回收結束,使用者的線程才恢複。

老年代GC觸發條件

  • 老年代空間不足時,會嘗試觸發Minor Gc,如果空間還是不足,則會觸發Major GC
  • 如果Major GC結束後,空間還是不足,會報OOM異常。
  • Major GC的速度比Minor GC慢10倍以上。

Full GC觸發條件

  • 程式調用System.gc(),會觸發Full GC,但不會立即去執行。
  • 老年代空間不足。
  • 方法區空間不足。
  • 通過Minor GC後仍能進入老年代的對象所占空間大于老年代剩餘可用空間。

5.元空間

JDK1.8後為什麼廢除永久代,引入元空間

  • 在之前的永久代中,它是堆的一部分,主要是在存儲類的中繼資料、靜态變量、常量等,這些資料的大小也不太容易控制和計算,開發人員對永久代進行調優會有很多的難度。永久代會對GC帶來不必要的複雜度,回收效率偏低。
  • 而用元空間替代永久代,這樣的話可以很好的解決這個問題,因為元空間是放在本地記憶體上的,簡而言之,隻要你伺服器記憶體還有,元空間基本就不會發生記憶體溢出等問題。

廢除永久代的好處

  • 由于類的中繼資料配置設定在本地記憶體上,這樣就說元空間的最大配置設定記憶體就是伺服器系統剩餘可用記憶體,不會遇到永久代時存在的記憶體溢出問題。
  • 将運作時常量池從永久代中分離出來,與類的中繼資料分開,提高了中繼資料的獨立性。
  • 将中繼資料從永久代剝離出來放到元空間,可以提升對中繼資料的管理,同時也提升GC效率。

元空間相關參數

  • -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就适當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,适當提高該值。
  • -XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。如果沒有使用該參數來設定類的中繼資料的大小,其最大可利用空間是整個系統記憶體的可用空間。JVM也可以增加本地記憶體空間來滿足類中繼資料資訊的存儲。但是如果沒有設定最大值,則可能存在bug導緻Metaspace的空間在不停的擴充,會導緻機器的記憶體不足;進而可能出現swap記憶體被耗盡;最終導緻程序直接被系統直接kill掉。如果設定了該參數,當Metaspace剩餘空間不足,會抛出:java.lang.OutOfMemoryError: Metaspace space。
  • -XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為配置設定空間所導緻的垃圾收集。
  • -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導緻的垃圾收集。

6.方法區

6.1方法區的了解

概念:

元空間、永久代是方法區具體的落地實作。方法區看作是一塊獨立于Java堆的記憶體空間,它主要是用來存儲所加載

的類資訊的,方法區是線程共享的。

特點:

  • 方法區與堆一樣是各個線程共享的記憶體區域。
  • 方法區在JVM啟動的時候就會被建立,并且它實際的實體記憶體空間和Java堆一樣可以是不連續的。
  • 方法區的大小跟堆一樣,可以選擇固定的大小或者動态變化。
  • 方法區的大小決定了系統可以儲存多少個類,如果系統定義了太多的類,導緻方法區溢出,虛拟機仍然會報OOM異常。
  • 關閉虛拟機就會釋放方法區區域。

6.2 方法區結構

類加載器将Class檔案加載到記憶體以後,将類的資訊存儲到方法區中。

方法區中存儲的内容:

  • 類型資訊(域資訊、方法資訊)
  • 運作時常量池
詳細了解JVM運作時記憶體

類型資訊

  • 這個類型的完整有效名稱(全名 = 包名.類名)
  • 這個類型直接父類的完整有效名(對于 interface或是java.lang. Object,都沒有父類)
  • 這個類型的修飾符( public, abstract,final的某個子集)
  • 這個類型直接接口的一個有序清單

域資訊

域資訊,即為類的屬性,成員變量

JVM必須在方法區中儲存類所有的成員變量相關資訊及聲明順序。

域的相關資訊包括:域名稱、域類型、域修飾符(pυblic、private、protected、static、final、volatile、transient的

某個子集)

方法資訊

  1. 方法名稱方法的傳回類型(或void)
  2. 方法參數的數量和類型(按順序)
  3. 方法的修飾符public、private、protected、static、final、synchronized、native,、abstract的一個子集
  4. 方法的位元組碼bytecodes、操作數棧、局部變量表及大小( abstract和native方法除外)
  5. 異常表( abstract和 native方法除外)。每個異常處理的開始位置、結束位置、代碼處理在程式計數器中的偏

    移位址、被捕獲的異常類的常量池索引

6.3 方法區設定

方法區的大小不必是固定的,可以根據應用的需要動态調整

  • jdk7及之前
  • 通過-xx:Permsize來設定永久代初始配置設定空間。
  • -XX:MaxPermsize來設定永久代最大可配置設定空間。64位的機器預設是82M。當JVM加載的類資訊容量超過了這個值,會報OOM異常:PermGen space。
  • jdk8及以後
  • 中繼資料區大小可以使用參數 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定。但是中繼資料區的 -XX:MaxMetaspaceSize預設是-1即沒有限制,不設定可以使用系統剩餘所有記憶體。
  • 如果中繼資料區發生溢出,虛拟機一樣會抛出異常OutOfMemoryError:Metaspace

7.運作時常量池

位元組碼檔案中,内部包含了常量池。

方法區中,内部包含了運作時常量池。

常量池:存放了編譯期間産生的各種字面量和符号引用。

運作時常量池:是常量池表在運作時的一種表現形式。