Class文件結構詳解,教你如何自己看懂二進制文件


什么是Class文件?

  Class文件是.java后綴文件通過編譯生成.class后綴的文件(以下簡稱Class文件)

  Class文件內部本質上是二進制的,但是這個二進制串,計算機是不能夠直接讀取並且執行的。也就是說,計算機看不懂,而我們的JVM解決了這個問題,JVM可以看作是一個翻譯官,它可以看懂,而且它也知道計算機想要什么樣子的二進制,所以它可以把Class文件的二進制翻譯成計算機需要的樣子。

  ps: 二進制碼只有0或1,每一個0或1代表一位(bit),八位就可以代表一個字節(byte)。這也是網上常說的Class文件是8位的二進制字節流的原因。

初探Class文件

  我們直接通過工具打開一個Class文件(具體工具在文章最下方),先看16進制的,因為2進制一眼下去肯定看懵了。我們先不用去管它.java文件具體是什么樣子的。首先跟着下面的方法看完一遍,你也能輕而易舉的去解讀自己寫的類的16進制源碼。(下面需要頻繁對照這個圖,可以看截圖出來再繼續閱讀)

       

Class文件結構概覽

  Class文件主要包括以下內容(大概了解就可以,不用完全記住)

    magic_number

    minor_version、major_version

    constant_pool_length、constant_pool

    access_flag

    this_class、super_class

    interfaces_count、interfaces

    field_count、fields

    methods_count、methods

    attrbutes_count、attributes

Class文件結構詳解

  接下來結合圖片和具體結構對文件結果進行字符級別的詳細解讀(都是按照字符順序依次解讀)

MagicNumber

  第一個字節CA就代表了兩個16進制的數也就是8位一個字節,同樣FE、BA、BE也是。這四個字節聯合起來稱為MagicNumber(魔法數~咖啡寶貝~)

  意義就是表明了這個文件是一個符合class文件定義規范的文件。使用者可以用class文件特有的格式規范來解讀它。

  接下來我們就用它特有的格式繼續進行解讀(其他的Class文件都是一樣的,不同點也僅僅是可變內容不同,比如自己寫的變量名、代碼之類的)

MinorVersion & MajorVersion

  繼續看000行的04和05位置上的兩個字節,這兩個字節聯合起來被稱為MinorVersion(小版本號)

  當前小版本號是 00 00也就是16位的二進制000...000(16個0),就表明了小版本號是0。

  聰明的同學已經想到了,有小版本,那肯定有大版本,沒錯緊接着還是000行的06和07位置上的兩個字節聯合被用作表示MajorVersion(大版本號),當前的大版本號是00 34(別忘了當前是16進制)換算成10進制就是52。

  綜合這4個字節的版本號就出來了:52.0,是不是很眼熟?沒錯,就是你每次打開class文件上面會出現的那一行提示信息當前版本52.0(也就是JDK1.8,如果是1.9的話有可能是53.0,這個有興趣可以自己去驗證一下)

ConstantPoolLength

  繼續,000行的08 09位置這兩字節被聯合起來表示常量池的大小。

  當前文件的常量池的大小是00 3E 換算成10進制就是62。(什么是常量池?這個先別擔心,下面會講到)當然雖然計算出來確實是62,但是實際上常量池內只保存了61個對象,原因是它預留了第0個位置不保存任何對象,也就用用作備用的一個位置。具體用來干什么,如果你仔細觀察Object類的Class文件,你會發現Object這個頂級類的父類索引指向的是這個0的槽位。

  那么接下來常量池的大小既然表示完了,理論上就應該是常量池的具體實現了,

  沒錯,你猜對了。但是!要注意一點上面雖然計算出了常量池的大小實際是61,這可不代表接下來只有61個字符用來存儲常量池。

ConstantPool

  常量池的表示十分復雜,如果把常量池弄明白,可以說Class文件已經懂了個大概了。因為常量池的對象類型有很多個,我們不一一解釋,而是轉為教你如何去自己看懂一個常量池里的對象!

  首先我們看000行0A列這個位置上的字節。好吧,好巧。。。它的值也是0A(不要被誤導就好)轉換成10進制就是10

  這個是什么呢?這個是常量池中第一個對象的tag(他的索引位置就是01),在后面的常量池對象類型表中可以找到tag為10的常量池對象,我們發現是CONSTANT_Methodref_info。

  繼續在常量池對象類型表中看他的細化結構,我們發現它有三個屬性,分別是tag、class_index、name_and_type_index。

  其中tag屬性就是我們剛才看到的0A也就是十進制10這個字節。

  再看class_index這個屬性類型是u2,同時故名思議,他是一個占用了2個字節的類索引。

    ps 一共有四種類型 u1 u2 u4 u8 分別代表了占用的字節數

  帶着這個推斷,我們去驗證以下,0A這個字節代表了tag那么接下來就是class_index這個占用了兩個字節的屬性了,也就是0A(列)的下面0B、0C兩列位置的兩個字節,從上圖可以發現,它們分別是00 0B,轉換成10進制就是11這個數值,也就說明了class_index=11。有了這個結論,我們就可以知道它是需要索引位置為11的這個常量池對象了。

  其實這里可以借助插件工具可視化的看到所有的常量池對象的索引

  這里先不用工具我們一個一個去數。最后我們可以得到第030行04列就是對應索引位置11的常量池對象,它的首個字節也就是它的tag是07,十進制7對應的正好是我們所期待的CONSTANT_Class_info這個類型。

  接下來就需要再看CONSTANT_Class_info這個類型的第二個屬性name_index值為52,也就是索引為52的那個對象。

  照葫蘆畫瓢,同樣通過工具或者直接挨個數,發現如下圖,它藏在第2E0行的0B列這個位置。

  ok按照慣例,繼續解析它的tag發現是01,tag1的類型是CONSTANT_Utf8_info,CONSTANT_Utf8_info第二個屬性占用了兩個字節,代表數組長度是十六進制的10,換算成10進制就是16。也就是說后面16個字節就是它所保存的內容!

  那么下面就是見證奇跡的時刻!我們看第一個字節6A,去對照ASCII表。發現它代表了" j "這個字母,再下面一個61代表字母",繼續往下面逐個翻譯,最終這16個謎一般的16進制字節碼,終於揭開了神秘的面紗,變成了java/lang/Object

  至此,對於上面我們看到的第一個常量池對象有個另一個初步的了解,他是一個類中的方法,是哪個類呢?哦!原來是Object類!

  這時候你可能有疑問,它是一個方法?那它究竟是哪個方法呢?這就就需要繼續解讀它的第二個屬性name_and_type_index,通過如上步驟(再次強調,有可視化工具更方便查看)我們最終會發現它是一個<init>方法,也就是Object類的初始化方法~

          

  一個常量池的對象就解析完畢了。剩下的可以繼續自己一點一點去解析。

  關於剩下的一下比如access_flag、this_class的索引super_class的索引,都可以按照這個方法去計算觀察。遇到疑惑的地方,除了去網上看,最好還是去官網找一些官方的資料。

  PS: 如果需要繼續解析剩下的內容可以留言,下次可以出一篇關於剩下的內容的解析。

      

  表格列出了大部分內容,其他內容可以去官網找資料看。排版凌亂,請見諒。

  推薦相關輔助工具,可以更高效的閱讀Class文件

  Idea(編譯器),jclasslib(idea內的一款插件,可以將class文件可視化的展示出來)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM