天天看點

JVM解毒——JVM與Java體系結構

你是否也遇到過這些問題?

  • 運作線上系統突然卡死,系統無法通路,甚至直接OOM
  • 想解決線上JVM GC問題,但卻無從下手
  • 新項目上線,對各種JVM參數設定一臉懵逼,直接預設,然後就JJ了
  • 每次面試都要重新背一遍JVM的一些原理概念性東西

這段廣告語寫的好,趁着在家辦公學習下JVM,先列出整體知識點

JVM解毒——JVM與Java體系結構
點贊+收藏 就學會系列,文章收錄在 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與Java體系結構

JVM的位置

JVM是運作在作業系統之上的,它與硬體沒有直接的互動。

JDK

(Java Development Kit) 是

Java

語言的軟體開發工具包(

SDK

)。

JDK

實體存在,是

Java Language

Tools

JRE

JVM

的一個集合。

JVM解毒——JVM與Java體系結構
JVM解毒——JVM與Java體系結構

JVM整體結構

JVM解毒——JVM與Java體系結構

JVM的架構模型

Java編譯器輸入的指令流基本上是一種基于棧的指令集架構,另外一種指令集架構則是基于寄存器的指令集架構。

兩種架構之間的差別:

  • 基于棧式架構的特點
    • 設計和實作更簡單,适用于資源受限的系統;
    • 避開了寄存器的配置設定難題,使用零位址指令方式配置設定;
    • 指令流中的指令大部分是零位址指令,其執行過程依賴于操作棧。指令集更小,編譯器容易實作;
    • 不需要硬體支援,可移植性更好,更好實作跨平台
  • 基于寄存器架構的特點
    • 典型的應用是X86的二進制指令集:比如傳統的PC以及Android的Davlik虛拟機;
    • 指令集架構則完全依賴硬體,可移植性差;
    • 性能優秀和執行更高效;
    • 花費更少的指令去完成一項操作;
    • 大部分情況下,基于寄存器架構的指令集往往都以一位址指令、二位址指令和三位址指令為主,而基于棧式架構的指令集卻是以零位址指令為主

由于跨平台性的設計,Java的指令都是根據棧來設計的。不同平台CPU架構不同,是以不能設計為基于寄存器的,優點是跨平台,指令集小,編譯器容易實作,缺點是性能下降,實作同樣的功能需要更多的指令。

分析基于棧式架構的JVM代碼執行過程

進入class檔案所在目錄,執行

javap -v xx.class

反解析(或者通過IDEA插件

Jclasslib

直接檢視),可以看到目前類對應的code區(彙編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等資訊。

JVM解毒——JVM與Java體系結構

以上圖中的 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》