應該不會誤人子弟的Java虛擬機器個人理解

Kingwe19發表於2020-10-27

何為Java虛擬機器?

Java虛擬機器(Java Virtual Machine 簡稱JVM)是執行所有Java程式的抽象計算機,是Java語言的執行環境。

JVM是在計算機作業系統記憶體上開闢的一塊區域,邏輯上又給JVM執行時資料區分成了如下五個部分,如下圖。

這裡要提及的是,黃色部分為執行緒私有資料,不會有執行緒安全問題;綠色部分為執行緒共享資料,可能會引發執行緒安全問題。

在這裡插入圖片描述
圖中元素的關係:如圖,Java原始檔首先在硬碟上經過編譯,成 .class(位元組碼)檔案之後,載入進記憶體,然後由執行引擎(CPU)分配時間排程去執行。

執行緒執行

假設現在有兩個執行緒(如下圖)要執行,我們可以看看其中的記憶體情況。
在這裡插入圖片描述
解釋一下:每建立一個執行緒,都會在JVM執行時資料區對應位置分配一小塊位置給他,這些位置總的加起來就構成了這個執行緒的記憶體分配狀況。我們看圖可以知道:在“ 棧”, “本地方法棧”, “程式計數器”這三塊位置分別都分配了一小塊位置。(這三塊位置都是執行緒私有的,我好像明白了執行緒私有的另一層含義)

現在用main執行緒來舉個例子

假設有這麼一段程式碼要執行:

class HelloWorld{
public int add(){
	int a = 1;
	int b = 2;
	int c = (a+b) * 100;
	return c;
}

public static void main(String[] args){
	HelloWorld app = new HelloWorld();
	int result = app.add();
	System.out.println(result);
}

}
  • :main方法壓棧執行,途中 add() 方法壓棧執行,add()方法執行完畢出棧,main方法執行完畢出棧,執行結束。

棧的細節圖:
在這裡插入圖片描述
      巨集觀的來說,就是上面說的那個樣子,但是細說,棧裡面的情況其實裡面還有上圖那麼多細節。這裡就稍微講一下,因為涉及了太多的組合語言太複雜了。
      情況就是,javap命令對位元組碼檔案進行反彙編生成彙編命令(如下圖),JVM執行下圖這些命令對成員變數進行一系列操作,這些操作就會用到上圖的 ”區域性變數表“, ”運算元棧“, ”方法出口“。
在這裡插入圖片描述
附圖一張看看這些命令的含義:(下圖中的棧指的是上上圖中的”運算元棧“)
在這裡插入圖片描述
對於區域性變數表和方法出口:add方法的方法出口是個int值(也就是返回值),程式碼中又是int result = app.add();這麼寫的,所以這個add方法的返回值又會被存到main方法的區域性變數表中。(不懂就看上面那個棧的細節圖)

  • 本地方法棧::JVM會分配一塊叫”本地方法棧“的區域去存用 Native關鍵字修飾的方法。當然我寫的這個例子中沒有體現,記住就好了。
  • 程式計數器執行當前執行緒所執行的位元組碼指令的(地址)行號。其實有了上上圖(就是黑色的那個cmd命令的那個圖),程式計數器就很好理解了,它就是圖中每個命令前面的那個序號(也叫行號)。它就是用來告訴執行引擎(CPU)應該執行序號對應的那一句指令。(每執行完一句指令程式計數器就會+1)
  • 執行緒共享區域,堆和方法區:為了方便,我用我之前截的圖,但是這應該也不影響對堆和方法區的理解?:
    在這裡插入圖片描述

圖的解釋:(當然這裡面寫的堆疊方法區和上面寫的是一一對應的!)

  1. 首先編譯成.class檔案載入進方法區;
  2. 然後主方法壓棧並執行;
  3. 物件建立在堆中完成(如圖);
  4. 方法呼叫如圖中綠色地址進行查詢,找到對應方法即馬上壓棧執行,執行結束立即出棧。

堆底層的劃分(JDK1.8中取消了持久代,取而代之的是元空間)

在這裡插入圖片描述

  • 老年代佔堆記憶體的三分之二,新生代佔三分之一;
  • 新生代中的記憶體佔比是 Eden:from:to = 8:1:1
由阿里面試題“為什麼Java需要效能調優?”引發的討論

產生的問題:實體記憶體會隨著JVM分配記憶體的增大最後而被耗盡。
在這裡插入圖片描述

變成 |
         |

在這裡插入圖片描述
於是引入“回收記憶體”:即JVM分配的記憶體達到一個臨界值,就對JVM的記憶體進行壓縮,其實壓縮的過程中就是效能調優。如下圖:
在這裡插入圖片描述

即:“在有限的空間做無限的事情”

Java是怎麼進行效能調優的?(垃圾回收)

在這裡插入圖片描述

  1. 新生代:(包括:Eden區、Survivor from區、Survivor to區)
  • 所有物件建立在新生代的Eden區,當Eden區滿時會進行一次Minor GC操作將Eden區進行回收,同時會有一個根的可達性判定GC Roots(其中有個可達性分析演算法),GC Roots判斷存活(存活是指還在被呼叫)的物件會被複制進入Survivor from區(同時年齡加1),其他物件就回收,這時Eden區清空;之後又可以在Eden區建立物件,滿了又進行Minor GC(如果from區有非存活物件這個時候也是直接回收),所有存活物件年齡又加一,一直反覆。對於Minor GC十五次之後依然存活的物件直接晉升進入老年代,實際上是為了保證Eden區具有充足的空間可用的一種策略
  • 保證一個Survivor區是空的,新生代Minor GC就是在兩個Survivor區之間相互複製存活物件,直到Survivor區超過一半為止,對於年齡等於十五的物件直接進入老年代,實際上是對Eden區到Survivor區過度的一種策略,是為了保證Eden區到Survivor區不會頻繁的進行復制一直存活的物件且對Survivor區也能保證不會具有太多的一直佔據的記憶體

晉升的條件:1. 物件年齡等於十五; 2. from區物件佔用超過百分之五十。

  1. 老年代:當Survivor區也滿了之後就通過Minor GC將物件複製到老年代 (在發生MinorGC之前,JVM會判斷之前每次晉升到老年代的平均大小是否大於老年代剩餘空間的大小,若大於則進行full GC) 。老年代也滿了的話,就將觸發Full GC,針對整個堆(包括新生代、老年代)進行垃圾回收。
  • Full GC有個問題:觸發Full GC會有一個STW(Stop-The-World)現象的出現。
  • Java中Stop-The-World機制簡稱STW,是在執行垃圾收集演算法時,Java應用程式的其他所有執行緒都被掛起(除了垃圾收集幫助器之外)。Java中一種全域性暫停現象,全域性停頓,所有Java程式碼停止,native程式碼可以執行,但不能與JVM互動;這些現象多半是由於gc引起。

Java效能調優:讓更少的物件進入老年代,減少STW的次數

相關文章