先上一張JVM體繫結構圖: 1)運行時數據區:經過編譯生成的位元組碼文件(class文件),由class loader(類載入子系統)載入後交給執行引擎執行。在執行引擎執行的過程中產生的數據會存儲在一塊記憶體區域。這塊記憶體區域就是運行時區域 2)程式計數器:用於記錄當前線程的正在執行的位元組碼指令位置。由 ...
先上一張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)中分配記憶體存儲,並把該指令位置“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中發生的事情。
參考:
《深入理解Java虛擬機:JVM高級特性與最佳實踐》一書
https://www.cnblogs.com/IUbanana/p/7067362.html