深扒JVM,對它進行「開膛破肚」式解析!

1. 打怪升級,你繞不開JVM

JVM,對Java程序員進階而言,是一個絕對繞不開,也不能繞開的話題。java

在你打怪升級、進階蛻變的路上,勢必會遇到項目上線中各類OOM、GC等問題,此時JVM的功底就相當重要了。程序員

這篇文章,咱們將從本身寫的代碼運行角度出發,將JVM「開膛破肚」。看看咱們寫的代碼,在JVM的各區域都幹了些啥?面試

多說一句,對於Java工程師的面試,JVM也是必問的一環,所以不管從面試仍是實際工做,你都頗有必要夯實本身的JVM功底。網絡

扯得有點遠,趕忙拉回來,立刻進入正題!併發

2. JVM 區域劃分

jvm的區域,大體有如下幾塊:jvm

  • 程序計數器
  • 虛擬機棧
  • 方法區
  • 本地方法棧

接下來咱們將JVM當成一個生物體,上述部分就是其不一樣器官。咱們將從本身寫的Java代碼如何經過JVM來運行這一角度,來分析JVM裏這些「器官」是如何支撐咱們的Java代碼跑起來的。性能

3. 程序計數器

假設咱們有以下的一個類,就是最最基本的一個HelloWorld而已:學習

public class HelloWorld {spa

public static void main(String[] args) {操作系統

System.out.println("Hello World");

}

}

上面那段代碼首先會存在於 「.java」 後綴的文件裏,這個文件就是java源代碼文件。

可是這個文件是面向咱們程序員的,計算機是看不懂這段代碼的。

因此此時就得經過編譯器,把「.java」後綴的源代碼文件編譯爲「.class」後綴的字節碼文件。

這個「.class」後綴的字節碼文件裏,存放的就是對你寫出來的代碼編譯好的字節碼了。

字節碼纔是計算器能夠理解的一種語言,而不是咱們寫出來的那一堆代碼。這個字節碼看起來大概是下面這樣的:

 

深扒JVM,對它進行「開膛破肚」式解析!

 

 

:這段字節碼並非徹底對照着HelloWorld那個類來寫的,就是給一段示例,讓你們知道「.java」翻譯成的「.class」是大概什麼樣子的。

大概給各位解釋一下,圖中好比「0: aload_0」這樣的就是「字節碼指令」,他對應了一條條機器指令,計算機只有讀到這種機器碼指令,才知道具體應該要幹什麼。

好比字節碼指令可能會讓計算機從內存裏讀取某個數據,或者把某個數據寫入到內存裏去。各類各樣的指令,會指示計算機去幹各類各樣的事情。

因此到這裏,你們首先明白的第一點:Java代碼是會被翻譯成字節碼的,不一樣字節碼指令指揮計算機幹不一樣的事情。

那麼在執行字節碼指令的時候,JVM裏的程序計數器做用是啥呢?

答案是:用來記錄每一個線程當前執行的字節碼指令的位置,即記錄當前線程目前執行到了哪一條字節碼指令。

在實際中,會有多個線程併發執行各類不一樣的代碼,因此每一個線程都有本身的程序計數器,專門記錄當前線程目前執行到了哪一條字節碼指令。

下圖更加清晰的展現出了他們之間的關係。

 

深扒JVM,對它進行「開膛破肚」式解析!

 

 

4. Java虛擬機棧

好,咱們接着來看。你們都清楚,Java代碼執行時,必定是線程來執行某個方法中的代碼。就算是最基礎的 HelloWorld ,也會有一個main線程來執行main方法裏的代碼。

在方法裏,常常會定義一些方法內的局部變量,好比下面這樣,在方法裏定義了一個局部變量「name」。

public void sayHello() {

String name = "hello";

}

因此我們JVM的這個「器官」就要出場了,JVM必須有一塊區域是來保存每一個方法內的局部變量等等數據的,這個區域就是Java虛擬機棧

爲何須要這個區域?由於每一個線程都會去執行各類方法的代碼,方法內還會嵌套調用其餘的方法,因此每一個線程都要有本身的Java虛擬機棧

若是線程執行了一個方法,那麼就會爲這個方法調用建立對應的一個棧幀

棧幀裏就有這個方法的局部變量表 、操做數棧、動態連接、方法出口等東西。這裏別的東西不太好理解,後面咱們再經過其餘文章詳細闡述,這裏先理解一個局部變量就能夠。

回到上面的例子,好比一個線程調用了上面寫的「sayHello」方法,那麼就會爲「sayHello」方法建立一個棧幀,壓入線程本身的Java虛擬機棧裏面去。

在棧幀的局部變量表裏就會有「name」這個局部變量,下圖展現了這個過程。

 

深扒JVM,對它進行「開膛破肚」式解析!

 

 

接着若是「sayHello」方法調用了另一個「greeting」方法 ,好比下面那樣的代碼:

 

深扒JVM,對它進行「開膛破肚」式解析!

 

 

這時會給「greeting」方法又建立一個棧幀,壓入線程的Java虛擬機棧。

想一想爲啥會這樣?由於sayHello方法裏開始執行greeting方法了,並且greeting方法的棧幀的局部變量表裏有一個「greet」變量,它是greeting方法的局部變量。

下圖展現了這個過程:

 

深扒JVM,對它進行「開膛破肚」式解析!

 

 

接着若是「greeting」方法執行完畢了,就會把「greeting」方法對應的棧幀從Java虛擬機棧裏給出棧,而後若是「sayHello」方法也執行完畢了,就會把「sayHello」方法也從Java虛擬機棧裏出棧。

這就是JVM中的Java虛擬機棧這個組件的做用。

這塊你們須要記住的是:調用執行任何方法時,都會給方法建立棧幀,而後入棧。

在棧幀裏存放了這個方法對應的局部變量之類的數據,包括這個方法執行的其餘相關的信息,方法執行完畢以後就出棧。

5. Java堆內存

JVM中有另一個很是關鍵的區域,就是Java堆,用來存放咱們在代碼中建立的各類對象的,好比下面的代碼:

public void teach(String name) {

Student student = new Student(name);

student.study();

}

上面的 「new Student(name)」 就建立了一個Student類型的對象實例,這個對象實例裏面會包含一些數據。相似Student這樣的對象,就會存放在Java堆內存裏。

而後方法的棧幀的局部變量表裏,這個引用類型的「student」局部變量就會存放Student對象的地址。你能夠認爲局部變量表裏的「student」指向了Java堆裏的Student對象。

下圖展現了這個過程:

深扒JVM,對它進行「開膛破肚」式解析!

 

6. 方法區 / Metaspace

這個方法區是在JDK 1.8之前的版本里,表明JVM中的一塊區域,主要是放相似Student類本身的信息的,平時用到的各類類的信息,都是放在這個區域裏的,還會有一些相似常量池的東西放在這個區域裏。

可是在JDK 1.8之後,這塊區域的名字改了,叫作「Metaspace」,能夠認爲是「元數據空間」這樣的意思,固然主要仍是存放咱們本身寫的各類類相關的信息。

7. 本地方法棧

在JDK不少底層API裏,好比IO相關的,NIO相關的,網絡Socket相關的,若是你們去看他內部的源碼,會發現不少地方都不是Java代碼。

不少地方都會去走native方法,去調用本地操做系統裏面的一些方法,可能調用的都是c語言寫的方法,或者一些底層類庫,好比下面這樣的:

public native int hashCode();

在調用這種native方法的時候,就會有線程對應的本地方法棧,這個裏面也是跟Java虛擬機棧相似的,也是存放各類native方法的局部變量表之類的信息。

關於這塊,這裏就不展開講了,後續有機會咱們再寫文章專門闡述。

8. 堆外內存

還有一個區域,不屬於JVM,經過NIO中的allocateDirect這種API,能夠在Java堆外分配內存空間,而後經過Java虛擬機裏的 DirectByteBuffer 來引用和操做堆外內存空間。

不少技術都會用這種方式,由於有一些場景下,堆外內存分配能夠提高性能。

9. 全文總結

最後作一點總結:

  • Java代碼經過JVM運行時,首先必定會一行一行執行編譯好的字節碼指令
  • 而後在執行的過程當中,對於方法的調用,會經過Java虛擬機棧來爲每一個方法建立棧幀,入棧和出棧,並且棧幀裏有方法的局部變量。
  • 對於對象的建立,會分配到Java堆內存裏去
  • 對於類信息的存儲,會放在方法區 / Metaspace這樣的區域裏
  • 另外有兩塊特殊的區域:
  • 本地方法棧:執行native方法時候用的棧,跟Java虛擬機棧是相似的
  • 堆外內存:能夠在Java堆外分配內存空間來存儲一些對象。

這裏分享一份我本身整理的【JVM體系結構與GC調優】PPT,但願對你們學習JVM有所幫助。

深扒JVM,對它進行「開膛破肚」式解析!

加入羣(Java填坑之路)789337293 便可免費獲取到!

相關文章
相關標籤/搜索