用一個“Hello World”讓你輕鬆理解JVM運行時數據區

博客地址:blog.ouyangsihai.cn

先上一張JVM體系結構圖:

用一個“Hello World”讓你輕鬆理解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

用一個“Hello World”讓你輕鬆理解JVM運行時數據區

(圖2)HelloWorld.class

用一個“Hello World”讓你輕鬆理解JVM運行時數據區


用一個“Hello World”讓你輕鬆理解JVM運行時數據區

(圖3)16進制的字節碼:

用一個“Hello World”讓你輕鬆理解JVM運行時數據區

B.接著,當我們通過IDE或者命令:java HelloWorld 運行這個class文件時,字節碼文件(class文件)通過類加載機制加載完畢交付給執行引擎執行;類加載機制把HelloWrold類的信息、靜態變量(例子中沒加)、常量(例子中沒加,常量會加載到方法區的常量池,這和靜態變量不一樣)等加載到方法區中,接下來如果需要創建該類的對象,需要通過new後面帶的參數到方法區進行查找類相關信息。

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

D.執行引擎執行指令過程中,按需調用本地庫接口以執行本地庫方法,如new指令、輸出屏幕等操作

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


分享到:


相關文章: