聊聊對java class 的理解

作爲一門解釋執行的語言,java爲什麼不像python那些直接使用源碼文件來執行呢? 爲什麼java要將java文件編譯成class文件呢?

我想主要的原因有3點:
1 減少程序體積
2 作爲強類型語言,編輯過程中發現錯誤
3 加快執行速度

        怎麼理解這三點呢? 對於第一點可以理解爲class文件其實是對java文件做了信息壓縮, 比如重複的字符串放到class文件的常量池裏面,使用的時候通過索引去引用, java的代碼翻譯成短小精悍的指令。 這都能大大的減少文件的大小。

        對於第二點,在編譯過程中,會檢查出很多語法錯誤,以及提示用戶對受檢異常的處理,這大大減少了運行時的錯誤。

        第三點加快執行速度,因爲編譯後的java指令,java虛擬機更容易識別。如果不經過編譯的話就要邊解析源文件邊查表找到對應的操作,這會浪費一部分時間。其實任何語言,最終執行都是cpu去解釋指令, 編譯型語言只是在編譯期間多做了一些事情,來減少運行時所做的工作。所以我們在編譯程序的時候要忍受編譯慢的痛苦,卻在運行時得到高效的回報。

說了這麼多隻是最近學class文件規範的一點心得體會。

在來概括下class文件的格式。

首先一個類會邊編譯成一個class,並不是一個文件被編譯成一個class。
然後對於class文件,主要包含的信息分爲以下三個層面。

1 class文件本身的信息,這包含魔數,class的版本信息,也就是給解釋器看到底能不能解釋。

2 常量池,這部分用於對字符串,引用的方法和類、字段。 其實還是圍繞字符串來展開

3 類,方法,屬性的描述。

另外方法中重要的屬性就是代碼屬性,代碼會使用常量池中的引用類型來引用方法,字段和字符串。 這用於區分指令操作的是字段,方法,還是字符串。

最後查看class文件信息可以用javap命令

javap -verbose xxx.class

附上一個java類和javap反編譯的結果對照

public class TestClass {
    private String hello = "hello";
    public String world = "world";
    protected int a;
    private static String test = "test";

    public static void main(String[] args) {
        System.out.println(test);
    }

    public void test() {
        System.out.println(hello);
    }
}

javap反編譯結果

Classfile /Users/ermei/art/TestClass.class
  Last modified 2020-2-19; size 659 bytes
  MD5 checksum 65b0891d7e686391604e9185073b6eb4
  Compiled from "TestClass.java"
public class TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #11.#27        // java/lang/Object."<init>":()V
   #2 = String             #12            // hello
   #3 = Fieldref           #10.#28        // TestClass.hello:Ljava/lang/String;
   #4 = String             #14            // world
   #5 = Fieldref           #10.#29        // TestClass.world:Ljava/lang/String;
   #6 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
   #7 = Fieldref           #10.#32        // TestClass.test:Ljava/lang/String;
   #8 = Methodref          #33.#34        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #9 = String             #17            // test
  #10 = Class              #35            // TestClass
  #11 = Class              #36            // java/lang/Object
  #12 = Utf8               hello
  #13 = Utf8               Ljava/lang/String;
  #14 = Utf8               world
  #15 = Utf8               a
  #16 = Utf8               I
  #17 = Utf8               test
  #18 = Utf8               <init>
  #19 = Utf8               ()V
  #20 = Utf8               Code
  #21 = Utf8               LineNumberTable
  #22 = Utf8               main
  #23 = Utf8               ([Ljava/lang/String;)V
  #24 = Utf8               <clinit>
  #25 = Utf8               SourceFile
  #26 = Utf8               TestClass.java
  #27 = NameAndType        #18:#19        // "<init>":()V
  #28 = NameAndType        #12:#13        // hello:Ljava/lang/String;
  #29 = NameAndType        #14:#13        // world:Ljava/lang/String;
  #30 = Class              #37            // java/lang/System
  #31 = NameAndType        #38:#39        // out:Ljava/io/PrintStream;
  #32 = NameAndType        #17:#13        // test:Ljava/lang/String;
  #33 = Class              #40            // java/io/PrintStream
  #34 = NameAndType        #41:#42        // println:(Ljava/lang/String;)V
  #35 = Utf8               TestClass
  #36 = Utf8               java/lang/Object
  #37 = Utf8               java/lang/System
  #38 = Utf8               out
  #39 = Utf8               Ljava/io/PrintStream;
  #40 = Utf8               java/io/PrintStream
  #41 = Utf8               println
  #42 = Utf8               (Ljava/lang/String;)V
{
  public java.lang.String world;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC

  protected int a;
    descriptor: I
    flags: ACC_PROTECTED

  public TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String hello
         7: putfield      #3                  // Field hello:Ljava/lang/String;
        10: aload_0
        11: ldc           #4                  // String world
        13: putfield      #5                  // Field world:Ljava/lang/String;
        16: return
      LineNumberTable:
        line 1: 0
        line 2: 4
        line 3: 10

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: getstatic     #7                  // Field test:Ljava/lang/String;
         6: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         9: return
      LineNumberTable:
        line 8: 0
        line 9: 9

  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #3                  // Field hello:Ljava/lang/String;
         7: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: return
      LineNumberTable:
        line 12: 0
        line 13: 10

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #9                  // String test
         2: putstatic     #7                  // Field test:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 5: 0
}
SourceFile: "TestClass.java"

另外寫了個簡單解析java class的程序 JavaClassParser

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章