1. 程式人生 > >一個“Hello World”理解JVM執行時資料區 侵立刪

一個“Hello World”理解JVM執行時資料區 侵立刪

轉自:https://mp.weixin.qq.com/s/as8iaQMoUcdL6iPdMrZOZg

先上一張JVM體系結構圖:

 

 

1)執行時資料區:經過編譯生成的位元組碼檔案(class檔案),由class loader(類載入子系統)載入後交給執行引擎執行。在執行引擎執行的過程中產生的資料會儲存在一塊記憶體區域。這塊記憶體區域就是執行時區域

 

2)程式計數器:用於記錄當前執行緒的正在執行的位元組碼指令位置。由於虛擬機器的多執行緒是切換執行緒並分配cpu執行時間的方式實現的,不同執行緒的執行位置都需要記錄下來,因此程式計數器是執行緒私有的

 

3)虛擬機器棧:虛擬機器棧是java方法執行的記憶體結構,虛擬機器會在每個java方法執行時建立一個“棧楨”,用於儲存區域性變量表,運算元棧,動態連結,方法出口等資訊。當方法執行完畢時,該棧楨會從虛擬機器棧中出棧。其中區域性變量表包含基本資料型別和物件引用;

 

   在java虛擬機器規範中,對這個區域規定了兩種異常狀態:如果執行緒請求的棧的深度大於虛擬機器允許的深度,將丟擲StackOverFlowError異常(棧溢位),如果虛擬機器棧可以動態擴充套件(現在大部分java虛擬機器都可以動態擴充套件,只不過java虛擬機器規範中也允許固定長度的java虛擬機器棧),如果擴充套件時無法申請到足夠的記憶體空間,就會丟擲OutOfmMemoryError異常(沒有足夠的記憶體)

 

4)本地方法棧:類似java方法的執行有虛擬機器棧,本地方法的執行則對應有本地方法棧

 

5)方法區:用於儲存已被虛擬機器載入的類資訊,常量,靜態變數,即時編譯器編譯後的程式碼等資料。執行緒共享(看儲存的資料就知道了)

 

java虛擬機器規範對方法區的限制非常寬鬆,除了和java堆一樣不需要連續的記憶體和可以選擇固定大小或者可擴充套件外,還可以選擇不實現垃圾收集。相對而言,垃圾收集在這個區域是比較少出現的,但並非資料進入了方法區就如永久代的名字一樣永久存在了。

 

這區域的記憶體回收目標重要是針對常量池的回收和型別的解除安裝,一般來說這個記憶體區域的回收‘成績’比較難以令人滿意。尤其是型別的解除安裝條件非常苛刻,但是這部分的回收確實是必要的。在sun公司的bug列表中,曾出現過的若干個嚴重的bug就是由於低版本的HotSpot虛擬機器對此區域未完成回收導致的記憶體溢位。

 

6)java堆(java Heap):堆的主要作用是存放程式執行過程中建立的物件例項,因為要存放的物件例項有可能會極多,因此也是虛擬機器記憶體管理中最大的一塊。並且由於硬體條件有限,所以需要不斷回收已“無用”的例項物件來騰出空間給新生成的例項物件;因此java的垃圾回收主要是針對堆進行回收的(還有方法區的常量池),java堆很多時候也被稱為GC堆(Garbage Collected Heap)。

 

7)類載入機制(Class Loader):類載入子系統是根據一個類的全限定名來載入該類的二進位制流到記憶體中,在JVM中將形成一份描述Class結構的元資訊物件(方法區),通過該元資訊物件可以獲知Class的結構資訊:如建構函式,屬性和方法等,Java允許使用者藉由這個Class相關的元資訊物件間接呼叫Class物件的功能。

 

好!說了這麼多關鍵字,再拿例子來講解一下這些關鍵字:

A.圖1是我們寫的HelloWorld.java,通過IDE或命令:javac HelloWorld 編譯生成16進位制的HelloWorld.class(位元組碼檔案,見圖3),想讀懂16進位制位元組可參考:一文讓你明白java位元組碼 ;但一般IDE會自動轉譯成圖2的指令;或者通過命令:javap -verbose HelloWorld 進行轉譯。

 

(圖1)HelloWorld.java

 

 

(圖2)HelloWorld.class

 

 

(圖3)16進位制的位元組碼:

 

 

B.接著,當我們通過IDE或者命令:java HelloWorld 執行這個class檔案時,位元組碼檔案(class檔案)通過類載入機制載入完畢交付給執行引擎執行;類載入機制把HelloWrold類的資訊、靜態變數(例子中沒加)、常量(例子中沒加,常量會載入到方法區的常量池,這和靜態變數不一樣)等載入到方法區中,接下來如果需要建立該類的物件,需要通過new後面帶的引數到方法區進行查詢類相關資訊。

 

C.類載入完後,虛擬機器會檢查程式的入口,虛擬機器中程式的執行入口為main函式,如HelloWorld.class中,,執行引擎找到main函式開始執行指令,並生成一個“楨棧”入棧至虛擬機器棧的棧頂;我們可以看到(圖2)在main方法下面的命令:0  new java.lang.StringBuilder [16]  表示建立一個String物件,建立的String物件例項會在java堆(Heap)中分配記憶體儲存(Java物件在JVM中的建立過程可以看這篇文章:Java物件是怎麼建立的(通過物件的建立,瞭解JVM記憶體結構)),並把該指令位置“0”記錄到當前執行緒的程式計數器中;3  dup 然後把該物件的引用壓入虛擬機器棧中,並把該指令位置“3”記錄到當前執行緒的程式計數器中;4  ldc <String "Hello"> [18] 從字串常量池(從jdk1.7開始,字串常量池被移動到java堆)載入字串常量Hello,並更新指令位置到程式計數器;...如果執行過程中有本地方法的指令,則會在本地方法棧中進行出入棧;這裡有個點注意一下,請看main函式指令16的位置: 16  new java.lang.StringBuilder [31] 這裡建立了一個StringBuilder物件,自jdk5開始已對這種型別的字串拼接進行了優化,具體自行谷歌補充。

 

D.執行引擎執行指令過程中,按需呼叫本地庫介面以執行本地庫方法,如new指令、輸出螢幕等操作

 

以上就是一個HelloWorld執行過程在JVM中發生的事情。