<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你管這破玩意叫 class?

          共 7781字,需瀏覽 16分鐘

           ·

          2021-03-23 18:45

            我是一個 .java 文件,名叫 FlashObject.java,叫我小渣就行。
          public class FlashObject {

              private String name;
              private int age;
              
              public String getName() {
                  return name;
              }

              public int add(int a, int b) {
                  return a + b;
              }

          }
          我馬上就要被 JVM 虛擬機老大加載并運行了,此時老虛走了過來。
          老虛:小渣呀,我馬上就要把你載了,你先瘦身一下,別占太大地方。
          小渣:好的,沒問題,等我十秒鐘。

          public class FlashObject{private String name;private int age;public int add(int a,int b){return a+b;}

          小渣:老虛,我瘦身好了,你看看。
          老虛:...,你是不是有病。
          小渣:怎么了,我把沒用的空格和回車啥的都去掉了,瘦身了好多呢!
          老虛:行吧,看你這智商,我就給你解釋解釋。你現在仍然是個文本文件,讓你瘦身是讓你定一個緊湊的數據結構來表示你這個 Java 文件里的信息,然后告訴我這個數據結構中每個字節(jié)都代表什么。
          小渣:哦哦,這樣啊。
          老虛:對啊,這樣一是方便我去加載,二是我這個虛擬機可不只是為你 Java 語言服務的,還有很多語言最終都可以轉換為我虛擬機識別的,你得設計一個通用的格式。
          小渣:嗯嗯,這回我明白啦!
           

          1
          類信息

           
          我的類名叫 FlashObject。
          先找個地方把它存起來,放開頭吧。

          這里的一個小方格是 1 個字節(jié),也就是 8 位。一個英文字母用 ASCII 碼表示為 1 個字節(jié),所以占一個方格,之后不再解釋。

          嚴謹的我又想到,這個類應該還有其父類
          雖然這個 .java 文件中沒寫,但也有其默認父類,Object。
          當然,我們得記錄下全類名
          java/lang/Object
          記在哪里呢?就緊跟在類名后面吧。
          誒不對,我這個類名呀,父類名呀,都是變長的,這樣緊挨著放,誰知道分界點在哪。
          不行不行,得分別在前面加個長度,就用兩字節(jié)表示吧。
          除了父類之外,還有接口名呢!雖然我們這個類沒寫,但也得定義出來。
          這個接口,和類名以及父類名稍有不同,因為可能有多個。
          但這不是事兒,先占用兩個字節(jié),表示接口的數量即可,之后一個一個的接口名仍然像上面那樣緊挨著排布。
          嗯,完美。

          2
          常量池


          慢慢地,我發(fā)現需要字符串名字的地方越來越多。
          除了剛剛的類名、父類名、接口名,還有屬性名、方法名、屬性的類名、方法的入參類型名、返回值類型名,等等等等。
          一方面,要是每個都這么展開寫下去,那文件格式會很亂,很多結構都是變長的。
          另一方面,很多字符串都是重復的,比如屬性 name 的類名 String,與方法 getName 的返回值類名 String,重復寫兩遍,就浪費了空間。
          因此,我決定,之前的方案作廢,設計一個新的結構來統(tǒng)一存儲這些字符串,我給他起名為常量池
          每個字符串都有一個索引與之對應,這個是可以計算出來的,不需要額外的字段。
          這樣,剛剛的類、父類、接口,就都可以指向這個索引了,也因此可以將長度固定下來。
          當然,現在這個常量池,僅僅存放了字符串。
          不難想到,還可能有整型、浮點型的值作為常量,甚至還有可能是個引用類型,然后這個引用類型再次指向常量池中的一個索引,有點像指針的指針。
          那這么多類型,必然就還需要一個記錄類型信息的地方,看來我們得將之前的設計改改。
          這樣,我們的常量池,就不單單可以存儲簡單的字符串常量了,而是可以根據不同類型,存儲與其相對應的數據結構的值。
          當然,我們常量池的整體結構還是不變的,只不過里面是類型豐富的結構。
          同樣,我們的整個設計,也沒有因為常量池的小改動,受到影響。
          OK,總結一下我們目前的整體方案。
          開頭存常量池,之后需要的常量就全往這里放,用一個索引指向它即可。
          緊接著存放類本身的相關信息,我們存放了當前類、父類以及接口的信息。
          看來老虛要求的瘦身工作,已經初具規(guī)模啦。
           

          3
          變量

           
          現在類本身的信息,已經找到合適的位置存放起來了,接下來我們存變量。
          變量也可能有多個,所以結構依然仿照我們之前的思路,開頭存數量,后面緊跟著各個存放變量的數據結構。
          至于變量用什么數據結構來存,是不是定長的,那就是我們接下來要設計的了。
          我們把其中一個變量拿出來,看看它有什么?
          private String name;
          非常清晰,private 這部分是變量的標記,String 是變量類型,name 是變量名字。

          先看標記部分

          除了 private,還有 public、protected、static、final、volatile、transient 等,有的可以放在一起,比如
          public static final String name;
          有的不能放在一起,比如
          public private String name; //錯誤
          我們用位圖的方式,每一個標記用一個位來表示(比如 public 在第一個位,private 在第二個位,static 在第四個位,final 在第五個位...),這樣不論如何排列組合,最終的值都是不一樣的。
          我們把這些標記所對應的值,都設計并記錄下來。

          標記

          public

          0x0001

          private

          0x0002

          protected

          0x0004

          static

          0x0008

          final

          0x0010

          volatile

          0x0040

          transient

          0x0080

          復合型的標記,就可以表現為將其相加,比如 public static,就是 0x0001 + 0x0008 = 0x0009。
          而這樣的賦值方式,不同排列組合后的和沒有重復的,且也能根據值很方便地反推出標記。
          不錯不錯,就這樣了。
          哦對了,類信息本身也有 public 呀 private 這些標記屬性,剛剛記錄類信息的時候忘了,先加上它,免得一會忘了!

          再看類型部分

          當前類型為 String,屬于一個引用數據類型中的類類型
          private String name;
          除此之外,還有八個基本數據類型,和引用類型中的數組類型
          為了占用更少的空間,我們將其用最少的符號來表示。

          符號表示

          類型

          B

          byte

          C

          char

          D

          double

          F

          float

          I

          int

          J

          long

          S

          short

          Z

          boolean

          LClassName ;

          [

          數組

          里的基本數據類型,和數組類型,都只占用一個 char 來表示,就只占了 1 個字節(jié)。
          如果是類,則占用了 L 和 ; 兩個字節(jié),再加上全類名所占的字節(jié)數。
          比如這里的 String 類型,用符號表示,就是
          Ljava/lang/String;
          但注意,這里的符號,也都可以存放在常量池中,而我們的變量結構中的類型描述符部分,只需要一個常量池索引即可。

          ok,第二部分也搞定了。

          再看名字部分

          名字部分沒什么好說的,相信你直接能猜到了,直接上圖。
          OK,兩字節(jié)的標記、兩字節(jié)的類型描述符、兩字節(jié)的變量名稱,這個就是我們一個變量的數據結構。
          把它放到我們最終的總視圖里。
          搞定!

          4
          方法


          方法也可能會有很多,我目前只有兩個方法,我們拿 add 方法來分析。
          public int add(int a, int b) {
              return a + b;
          }
          當然更準確地說,我還有個沒寫出來的構造方法。
          總之,可能會有很多。
          不過有了設計變量的經驗,方法的數據結構很快就有了雛形。
          標記部分,和變量標記部分的思路一樣,值也差不多,我們也給他們賦上值就好了。

          標記

          public

          0x0001

          private

          0x0002

          protected

          0x0004

          static

          0x0008

          final

          0x0010

          volatile

          0x0040

          transient

          0x0080

          synchronized

          0x0020

          native

          0x0100

          abstract

          0x0400

          方法描述符,說的是方法的入參與返回值,比如我們的:
          int add(int a, int b);
          入參與返回值的類型符號表示,與上面變量類型的符號表示完全一樣,只不過多了一個 void 類型。

          符號表示

          類型

          B

          byte

          C

          char

          D

          double

          F

          float

          I

          int

          J

          long

          S

          short

          Z

          boolean

          LClassName ;

          [

          數組

          V

          void

          由于有多個參數類型,所以要定一個整體的格式,而整個描述符的格式為:
          ( 參數1類型 參數2類型 ... ) 返回值類型
          比如我們的
          int add(int a, int b);
          就表示為
          (II)I
          是不是非常精簡了?同樣,這也是個字符串,也可以存儲在常量池里,就不再贅述。
          (至于參數 a 和 b 這個名字,不需要保存起來,實際上在轉換的字節(jié)碼以及實際虛擬機中運行時,只需要知道局部變量表中的位置即可,叫什么名字都無所謂)
          方法名稱,我們再熟悉不過了,放常量池!
          ok,前三個說完了。最后一個,就有意思了。

          代碼、異常、注解等。以看到,有相當多的信息需要記錄。
          比如我寫這樣的方法。
          @RequestMapping()
          public String function(String a) throws Exception {
              return a;
          }
          那就會有代碼部分、異常、注解等需要錄入的信息。
          但似乎除了代碼部分之外,其他部分都不是每個方法都有的,如果都定義出來,豈不是浪費空間,那怎么辦呢?
          我們效仿常量池的做法,把這些部分都叫“方法的屬性”,一個方法可能有多個屬性,設計結構如下。
          這樣,方法具有哪些屬性,按需添加進來就好,如果不需要這個屬性,也不用浪費空間,完美!
          回過頭看我們的這個方法。
          public int add(int a, int b) {
              return a + b;
          }
          剛剛方法簽名部分已經都解決了,只剩下代碼
          return a + b;
          這個要怎樣存放呢?
          之前聽老虛說過,JVM 識別的是一種叫字節(jié)碼的東西,所以我要把 Java 語言寫出的代碼,轉換為字節(jié)碼。
          這部分很復雜,就不展開說我的過程了,經過一番努力后,我把這一行簡簡單單的代碼轉換為了字節(jié)碼。
          1B 1C 60 AC
          一共占四個字節(jié)。
          我把這四個字節(jié),就放在剛剛代碼類型的屬性中。
          ok,大功告成。
          回過頭,我們將之前的方法部分補充完整。
          再將這個結構,添加到我們全局結構中。
          完美!

          5
          class


          我把我轉換為了這樣的結構,并帶著這個最終的設計稿,去找了老虛。
          老虛:嗯!還真不賴!
          小渣:那當然,我可是研究了好久呢。
          老虛:不過,我再給你改改,在開頭加些東西把。
          小渣:老虛,你這加的是啥呀?
          老虛:一看你就沒經驗。
          魔數一般用來識別這個文件的格式,通過文件名后綴的方式不靠譜,一般有格式的文件都會有個魔數的。
          后面兩個用來標識一下版本號,不同版本可能數據結構和支持的功能不一樣,這個今后會有用的!
          小渣:原來如此,還是你老虛見多識廣。可是你說用來識別這個文件的格式,我這個文件是啥呀?
          老虛:你這個破玩意,就叫它 class 文件吧!

          FlashObject.class


          后記





          根據 Java 虛擬機規(guī)范,Java Virtual Machine Specification Java SE 8 Edition,一個 class 文件的標準結構,是這樣的。

          ClassFile {
              u4             magic;
              u2             minor_version;
              u2             major_version;
              u2             constant_pool_count;
              cp_info        constant_pool[constant_pool_count-1];
              u2             access_flags;
              u2             this_class;
              u2             super_class;
              u2             interfaces_count;
              u2             interfaces[interfaces_count];
              u2             fields_count;
              field_info     fields[fields_count];
              u2             methods_count;
              method_info    methods[methods_count];

              u2             attributes_count;
              attribute_info attributes[attributes_count];
          }

          我們的設計與它幾乎相同。

          只有后兩項,我們沒有涉及到,本身也不是重點。

          常量池中的類型,有以下幾種。
          Constant Type
          Value
          CONSTANT_Class
          7
          CONSTANT_Fieldref
          9
          CONSTANT_Methodref
          10
          CONSTANT_InterfaceMethodref
          11
          CONSTANT_String
          8
          CONSTANT_Integer
          3
          CONSTANT_Float
          4
          CONSTANT_Long
          5
          CONSTANT_Double
          6
          CONSTANT_NameAndType
          12
          CONSTANT_Utf8
          1
          CONSTANT_MethodHandle
          15
          CONSTANT_MethodType
          16
          CONSTANT_InvokeDynamic
          18

          如果想了解 class 文件的全部細節(jié),最好的辦法就是閱讀官方文檔,也就是 Java 虛擬機規(guī)范的第四部分。

          Chapter 4. The class File Format

          這里的鏈接可以直接定位:

          https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2

          不要覺得官方文檔晦澀難懂,這個部分還是非常清晰明了的,大多數博客基本上對格式的講解都缺斤少兩,而且說得也不形象,還不如直接閱讀官方文檔呢。

          還有一個好的方式,就是直接觀察 class 文件的二進制結構解析,這里推薦一個工具

          classpy

          用這個工具打開一個 class 文件,是這個樣子。

          左邊解析好的樹型結構,可以直接和右邊的 class 文件的二進制內容相對應,非常好用。

          最后,希望大家找時間用這個工具分析一個復雜的 class 文件,會很有幫助的。祝大家學會 class 文件。

          瀏覽 26
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青娱乐国产极品 | 日本久久直播 | av中文字| 91福利影院 | 四季AV一区二区凹凸懂色 |