天天看點

了解Java虛拟機(2)之.class檔案加載過程了解Java虛拟機(2)之.class檔案加載過程

了解Java虛拟機(2)之.class檔案加載過程

讀《深入了解Java虛拟機》-周志明 讀書筆記

虛拟機隻能執行.class檔案,在.class檔案加載過程中,生命周期包括:加載,驗證,準備,解析,初始化,使用,解除安裝

了解Java虛拟機(2)之.class檔案加載過程了解Java虛拟機(2)之.class檔案加載過程
  • 加載将.clss檔案加載進虛拟機,加載來源有
    • 1.常見jar包;
    • 2.網絡擷取(dubbo的RPC是典型,底層通過java的RMI方法,通過固定協 議,将遠端生成者接口,對象序列化,消費者調用);
    • 3.運作時動态生成,典型代表反射技術;
    • 4.從其他檔案,如jsp;
    • 5.通過資料庫讀取,(沒見到過,書裡說中間件伺服器)
    加載完成後,虛拟機就會按照.class檔案裡面的内容相應的存儲到記憶體的方法區(方法區是虛拟機加載類資訊,常量,靜态變量,即使編譯後的代碼,是線程共享的,簡單講就是加載的東西放在這裡面),加載是通過ClassLoader這個加載,這個加載機制是通過(雙親委派模型,後面詳細講雙親委派模型,簡單講就是JVM有一個系列的繼承加載,自己也可以自定義ClassLoader,先通過自己的ClassLoader,在通過JVM的ClassLoader),平常使用Tomcat,Jetty,這些容器都是有自己的ClassLoader,用過maven配置jetty啟動時,都會在pom.xml配置标簽,這個就是去配置jetty的ClassLoader。
  • 驗證

    驗證是連接配接階段的第一步,并不是加載完了才驗證(這樣的太傻瓜式了,要是很多.class檔案,在最開始加載就有錯誤,等加載完了再驗證那就不用工作了),驗證和加載是相輔相成的。在.java編譯成.class檔案裡講過.class打頭就是個”cafebabe”的魔數,首先驗證這個,不是這個打頭的必然報錯,接着看看版本号,版本号是不是java沒有的(比方搞個java6.0,必然錯了),然後看常量類型啊,驗證相當于看這個.class檔案是否符合JVM規範,用過Eclipse的就知道,每次”ctrl+s”就能看到自己寫的代碼有沒有錯誤,因為eclipse就是有自己的CLassLoader,即時編譯,你一儲存就給你編譯了.class,打開檔案夾就能看到class目錄的檔案夾,然後通過去驗證編譯後的.class是不是符合JVM規範,然後錯了就各種紅叉叉,iead就沒有即時編譯,”ctrl+s”就找不到對應的class檔案夾,eclipse的驗證不是虛拟機的驗證,這隻是eclipse自己根據jVM規範驗證的,編譯後的.class檔案JVM在加載的過程中,還是要自己驗證的,不過虛拟機有個參數可以設定不驗證(-Xverify:none),這樣可以加快加載速度,當然前提是你相信.class符合規範,驗證是很重要的一件事,不然怎麼承受惡意的攻擊,使得代碼更安全,當然驗證也會使得加載時間長,在安全和時間上,還是選擇更安全些,畢竟java大都都是用來做企業級應用的,安全是最重要的。

  • 準備

    準備階段是正式給類變量(static修飾的變量)配置設定記憶體并設定類變量的初始值,執行個體變量(不是static修飾的類層面的變量)會在對象執行個體化時随着對象一起配置設定在java堆中,要注意的是初始值的含義,和final修飾的執行個體變量,初始值指資料類型的0值,下面a在這個時候初始值為0;final修飾的變量這個時候會給值,c此時的值就是為3;(感覺final修飾符有些破壞了面向對象的設計,也不知道這個設計的初衷)

public Class Test(){
    private static int a = ;//類變量
    private int b =  ;//執行個體變量
    private final int c =  ;
}
           
  • 解析

    解析是虛拟機将常量池(常量池位于方法區)的符号引用替換成直接引用的過程,先解釋下符号引用,直接引用

    符号引用:

public class Test {
    public static  int sa = new Random().nextInt();
    }
    public class TestClass {
    //這裡就是符号引用
    public static  int sa = Test.sa;
    public static  int sb = ;

}
           

編譯下上上面的,再反編譯過來如下(javap -verbose TestClass.class):

D:\learn\myLearn\readBook\src\main\java\JVM>javap -verbose TestClass.class
Classfile /D:/learn/myLearn/readBook/src/main/java/JVM/TestClass.class
  Last modified --; size  bytes
  MD5 checksum ed1ae561b16a0cfecbdf7518c947cb
  Compiled from "TestClass.java"
public class JVM.TestClass
  SourceFile: "TestClass.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          ##         //  java/lang/Object."<init>":()
   # = Fieldref           ##        //  JVM/Test.sa:I
   # = Fieldref           ##         //  JVM/TestClass.sa:I
   # = Fieldref           ##         //  JVM/TestClass.sb:I
   # = Class              #            //  JVM/TestClass
   # = Class              #            //  java/lang/Object
   # = Utf8               sa
   # = Utf8               I
   # = Utf8               sb
  # = Utf8               <init>
  # = Utf8               ()V
  # = Utf8               Code
  # = Utf8               LineNumberTable
  # = Utf8               <clinit>
  # = Utf8               SourceFile
  # = Utf8               TestClass.java
  # = NameAndType        #:#        //  "<init>":()V
  # = Class              #            //  JVM/Test
  # = NameAndType        #:#          //  sa:I
  # = NameAndType        #:#          //  sb:I
  # = Utf8               JVM/TestClass
  # = Utf8               java/lang/Object
  # = Utf8               JVM/Test
{
  public static int sa;
    flags: ACC_PUBLIC, ACC_STATIC

  public static int sb;
    flags: ACC_PUBLIC, ACC_STATIC

  public JVM.TestClass();
    flags: ACC_PUBLIC
    Code:
      stack=, locals=, args_size=
         : aload_0
         : invokespecial #                  // Method java/lang/Object."
":()V
         4: return
      LineNumberTable:
        line 8: 0

  static {};
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  // Field JVM/Test.sa:I
         3: putstatic     #3                  // Field sa:I
         6: bipush        114
         8: putstatic     #4                  // Field sb:I
        11: return
      LineNumberTable:
        line 9: 0
        line 11: 6
}
           

截部分看看

java/lang/Object."<init>":()
   # = Fieldref           ##        //  JVM/Test.sa:I
   # = Fieldref           ##         //  JVM/TestClass.sa:I

   static {};
    flags: ACC_STATIC
    Code:
      stack=, locals=, args_size=
         : getstatic     #                  // Field JVM/Test.sa:I
         : putstatic     #                  // Field sa:I 
         : bipush        
         : putstatic     #                  // Field sb:I  
           
最後satatic{}可以看到Test.java中sa(#3 = Fieldref          #5.#19 //  JVM/TestClass.sa:I)變量指向的TestClass(JVM/Test.sa:I 3: putstatic     #3   // Field sa:I ),初始化就是将這種指向變成指向new Random().nextInt(),的具體記憶體位址,這樣才能真正的調用直接引用就是直接new 對象了,将句柄指向對象的指針位址;在解析的過程中,遇到引用其他的類,就去觸發對應類的加載,最後都是直接引用
           
  • 初始化

初始化是類加載的最後一步,這個階段才是真正執行類中定義的代碼,在準備階段,變量已經指派過系統要求的值,初始化階段根據我們寫代碼的主觀習慣去初始化變量和其他資源。相當于程式真正運作起來。