<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?

          共 7793字,需瀏覽 16分鐘

           ·

          2021-03-14 07:38

          低并發(fā)編程,周一很頹廢,周四很硬核
          我是一個 .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;}

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

          1
          類信息

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

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

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

          2
          常量池


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

          3
          變量

           
          現(xiàn)在類本身的信息,已經(jīng)找到合適的位置存放起來了,接下來我們存變量。
          變量也可能有多個,所以結構依然仿照我們之前的思路,開頭存數(shù)量,后面緊跟著各個存放變量的數(shù)據(jù)結構。
          至于變量用什么數(shù)據(jù)結構來存,是不是定長的,那就是我們接下來要設計的了。
          我們把其中一個變量拿出來,看看它有什么?
          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

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

          再看類型部分

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

          符號表示

          類型

          B

          byte

          C

          char

          D

          double

          F

          float

          I

          int

          J

          long

          S

          short

          Z

          boolean

          LClassName ;

          [

          數(shù)組

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

          ok,第二部分也搞定了。

          再看名字部分

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

          4
          方法


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

          標記

          public

          0x0001

          private

          0x0002

          protected

          0x0004

          static

          0x0008

          final

          0x0010

          volatile

          0x0040

          transient

          0x0080

          synchronized

          0x0020

          native

          0x0100

          abstract

          0x0400

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

          符號表示

          類型

          B

          byte

          C

          char

          D

          double

          F

          float

          I

          int

          J

          long

          S

          short

          Z

          boolean

          LClassName ;

          [

          數(shù)組

          V

          void

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

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

          5
          class


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

          FlashObject.class


          后記





          根據(jù) 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

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

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

          classpy

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

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

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

          完~
          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产剧情一区二区 | 激情五月天丁香网 | 国产福利一区二区在线观看 | 亚洲欧洲一区二区三区视频 | 亚洲男人的天堂在线视频 |