你是否也遇到過這些問題?
- 運行線上系統突然卡死,系統無法訪問,甚至直接OOM
- 想解決線上JVM GC問題,但卻無從下手
- 新項目上線,對各種JVM參數設置一臉懵逼,直接默認,然后就JJ了
- 每次面試都要重新背一遍JVM的一些原理概念性東西
這段廣告語寫的好,趁着在家辦公學習下JVM,先列出整體知識點

點贊+收藏 就學會系列,文章收錄在 GitHub JavaEgg ,N線互聯網開發必備技能兵器譜
Java開發都知道JVM是Java虛擬機,上學時還用過的VM也叫虛擬機,先比較一波
虛擬機與Java虛擬機
所謂虛擬機(Virtual Machine),就是一台虛擬的計算機。它是一款軟件,用來執行一系列虛擬計算機指令。大體上,虛擬機可以分為系統虛擬機和程序虛擬機。
- Visaual Box,VMware就屬於系統虛擬機,它們完全是對物理計算機的仿真,提供了一個可運行完整操作系統的軟件平台
- 程序虛擬機的典型代表就是Java虛擬機,它專門為執行單個計算機程序而設計,在Java虛擬機中執行的指令我們稱為Java字節碼指令
JVM 是什么
JVM 是 Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規范,它是一個虛構的計算機,是通過在實際的計算機上仿真模擬各種計算機功能來實現的。
Java虛擬機是二進制字節碼的運行環境,負責裝載字節碼到其內部,解釋/編譯為對應平台的機器指令執行。每一條Java指令,Java虛擬機規范中都有詳細定義,如怎么取操作數,怎么處理操作數,處理結果放在哪里。
特點
- 一次編譯,到處運次(跨平台)
- 自動內存管理
- 自動垃圾回收功能
字節碼
我們平時所說的java字節碼,指的是用java語言編寫的字節碼,准確的說任何能在jvm平台上執行的字節碼格式都是一樣的,所以應該統稱為jvm字節碼。
不同的編譯器可以編譯出相同的字節碼文件,字節碼文件也可以在不同的jvm上運行。
Java虛擬機與Java語言沒有必然的聯系,它只與特定的二進制文件格式——Class文件格式關聯,Class文件中包含了Java虛擬機指令集(或者稱為字節碼、Bytecodes)和符號集,還有一些其他輔助信息。
Java代碼執行過程

JVM的位置
JVM是運行在操作系統之上的,它與硬件沒有直接的交互。
JDK(Java Development Kit) 是 Java 語言的軟件開發工具包(SDK)。JDK 物理存在,是 Java Language、Tools、JRE 和 JVM 的一個集合。


JVM整體結構

JVM的架構模型
Java編譯器輸入的指令流基本上是一種基於棧的指令集架構,另外一種指令集架構則是基於寄存器的指令集架構。
兩種架構之間的區別:
- 基於棧式架構的特點
- 設計和實現更簡單,適用於資源受限的系統;
- 避開了寄存器的分配難題,使用零地址指令方式分配;
- 指令流中的指令大部分是零地址指令,其執行過程依賴於操作棧。指令集更小,編譯器容易實現;
- 不需要硬件支持,可移植性更好,更好實現跨平台
- 基於寄存器架構的特點
- 典型的應用是X86的二進制指令集:比如傳統的PC以及Android的Davlik虛擬機;
- 指令集架構則完全依賴硬件,可移植性差;
- 性能優秀和執行更高效;
- 花費更少的指令去完成一項操作;
- 大部分情況下,基於寄存器架構的指令集往往都以一地址指令、二地址指令和三地址指令為主,而基於棧式架構的指令集卻是以零地址指令為主
由於跨平台性的設計,Java的指令都是根據棧來設計的。不同平台CPU架構不同,所以不能設計為基於寄存器的,優點是跨平台,指令集小,編譯器容易實現,缺點是性能下降,實現同樣的功能需要更多的指令。
分析基於棧式架構的JVM代碼執行過程
進入class文件所在目錄,執行javap -v xx.class反解析(或者通過IDEA插件Jclasslib直接查看),可以看到當前類對應的code區(匯編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等信息。

以上圖中的 1+2 為例說明:
Classfile /Users/starfish/workspace/myCode/starfish-learning/starfish-learn/target/classes/priv/starfish/jvm/JVM1.class
Last modified 2020-2-7; size 487 bytes
MD5 checksum 1a9653128b55585b2745270d13b17aaf
Compiled from "JVM1.java"
public class priv.starfish.jvm.JVM1
SourceFile: "JVM1.java"
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // priv/starfish/jvm/JVM1
#3 = Class #24 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lpriv/starfish/jvm/JVM1;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 i
#16 = Utf8 I
#17 = Utf8 j
#18 = Utf8 k
#19 = Utf8 MethodParameters
#20 = Utf8 SourceFile
#21 = Utf8 JVM1.java
#22 = NameAndType #4:#5 // "<init>":()V
#23 = Utf8 priv/starfish/jvm/JVM1
#24 = Utf8 java/lang/Object
{
public priv.starfish.jvm.JVM1();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lpriv/starfish/jvm/JVM1;
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1 //冒號前的數字表示程序計數器的數,常量1入棧
1: istore_1 //保存到1的操作數棧中,這里的1表示操作數棧的索引位置
2: iconst_2
3: istore_2
4: iload_1 //加載
5: iload_2
6: iadd //常量出棧,求和
7: istore_3 //存儲到索引為3的操作數棧
8: return
LineNumberTable:
line 6: 0
line 7: 2
line 8: 4
line 9: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
2 7 1 i I
4 5 2 j I
8 1 3 k I
MethodParameters: length = 0x5
01 00 0D 00 00
}
JVM生命周期
虛擬機的啟動
Java虛擬機的啟動是通過引導類加載器(Bootstrap Class Loader)創建一個初始類(initial class)來完成的,這個類是由虛擬機的具體實現指定的。
虛擬機的執行
- 一個運行中的Java虛擬機有着一個清晰的任務:執行Java程序
- 程序開始執行時它才運行,程序結束時它就停止
- 執行一個所謂的Java程序的時候,真正執行的是一個叫做Java虛擬機的進程
- 你在同一台機器上運行三個程序,就會有三個運行中的Java虛擬機。 Java虛擬機總是開始於一個main()方法,這個方法必須是公有、返回void、只接受一個字符串數組。在程序執行時,你必須給Java虛擬機指明這個包含main()方法的類名。
虛擬機的退出
有以下幾種情況:
- 程序正常執行結束
- 程序在執行過程中遇到了異常或錯誤而異常終止
- 由於操作系統出現錯誤而導致Java虛擬機進程終止
- 某線程調用Runtime類或System類的exit方法,或Runtime類的halt方法,並且Java安全管理器也允許這次exit或halt操作
- 除此之外,JNI(Java Native Interface)規范描述了用
JNI Invocation API來加載或卸載Java虛擬機時,Java虛擬機的退出情況
Java和JVM規范
Java Language and Virtual Machine Specifications
JVM發展歷程
JDK 版本升級不僅僅體現在語言和功能特性上,還包括了其編譯和執行的 Java 虛擬機的升級。
- 1990年,在Sun計算機公司中,由Patrick Naughton、MikeSheridan及James Gosling領導的小組Green Team,開發出的新的程序語言,命名為Oak,后期命名為Java
- 1995年,Sun正式發布Java和HotJava產品,Java首次公開亮相
- 1996 年,JDK 1.0 發布時,提供了純解釋執行的 Java 虛擬機實現:Sun Classic VM。
- 1997 年,JDK 1.1 發布時,虛擬機沒有做變更,依然使用 Sun Classic VM 作為默認的虛擬機
- 1998 年,JDK 1.2 發布時,提供了運行在 Solaris 平台的 Exact VM 虛擬機,但此時還是用 Sun Classic VM 作為默認的 Java 虛擬機,同時發布了JSP/Servlet、EJB規范,以及將Java分成J2EE、J2SE、J2ME
- 2000 年,JDK1.3 發布,默認的 Java 虛擬機由 Sun Classic VM 改為 Sun HotSopt VM,而 Sun Classic VM 則作為備用虛擬機
- 2002 年,JDK 1.4 發布,Sun Classic VM 退出商用虛擬機舞台,直接使用 Sun HotSpot VM 作為默認虛擬機一直到現在
- 2003年,Java平台的Scala正式發布,同年Groovy也加入了Java陣營
- 2004年,JDK1.5發布,同時JDK1.5改名為JDK5.0
- 2006年,JDK6發布,同年,Java開源並建立了OpenJDK。順理成章,Hotspot虛擬機也成為了OpenJDK默認虛擬機
- 2008年,Oracle收購BEA,得到了JRockit虛擬機
- 2010年,Oracle收購了Sun,獲得Java商標和HotSpot虛擬機
- 2011年,JDK7發布,在JDK1.7u4中,正式啟用了新的垃圾回收器G1
- 2014年,JDK8發布,用元空間MetaSpace取代了PermGen
- 2017年,JDK9發布,將G1設置為默認GC,替代CMS
Sun Classic VM
- 世界上第一款商用 Java 虛擬機。1996年隨着Java1.0的發布而發布,JDK1.4時完全被淘汰;
- 這款虛擬機內部只提供解釋器;
- 如果使用JIT編譯器,就需要進行外掛。但是一旦使用了JIT編譯器,JIT就會接管虛擬機的執行系統,解釋器就不再工作,解釋器和編譯器不能配合工作;
- 現在hotspot內置了此虛擬機
Exact VM
- 它的執行系統已經具備了現代高性能虛擬機的雛形:如熱點探測、兩級即時編譯器、編譯器與解析器混合工作模式等;
- 使用准確式內存管理:虛擬機可以知道內存中某個位置的數據具體是什么類型;
- 在商業應用上只存在了很短暫的時間就被更優秀的 HotSpot VM 所取代
Sun HotSpot VM
- 它是 Sun JDK 和 OpenJDK 中所帶的虛擬機,也是目前使用范圍最廣的 Java 虛擬機;
- 繼承了 Sun 之前兩款商用虛擬機的優點(如准確式內存管理),也使用了許多自己新的技術優勢,如熱點代碼探測技術(通過執行計數器找出最具有編譯價值的代碼,然后通知 JIT 編譯器以方法為單位進行編譯;
- Oracle 公司分別收購了 BEA 和 Sun,並在 JDK8 的時候,整合了 JRokit VM 和 HotSpot VM,如使用了 JRokit 的垃圾回收器與 MissionControl 服務,使用了 HotSpot 的 JIT 編譯器與混合的運行時系統。
BEA JRockit VM
- 專注於服務器端應用,內部不包含解析器實現;
- 號稱是世界上最快的JVM
IBM J9 VM
- 全稱:IBM Technology for Java Virtual Machine,簡稱IT4J,內部代號:J9
- 市場定位於HotSpot接近,服務器端、桌面應用、嵌入式等多用途VM
- 目前是有影響力的三大商用虛擬機之一
虛擬機有很多,此外還有Azul VM、Liquid VM、Apache Harmony、TaobaoJVM、Graal VM等
參考
《深入理解Java虛擬機》
《尚硅谷JVM》
