目錄
- 前言
- 如何閱讀class檔案
- 基本概念
- 無符號數&表
- 常量池
- 魔數(magic number) & 版本號
- 常量池
- 訪問標志
- 類引索&父類引索&介面引索集合
- 欄位表集合
- 方法表集合
- 屬性表集合
- Code屬性
- 使用javap決議class檔案
- 查閱表格
前言
最近在研究Java的反射和動態代理,發現使用這兩個Java神器需要了解.class檔案的位元組碼,于是翻閱了相關資料,在這篇博客中進行一番整理,也作為自己學習的記錄,
如何閱讀class檔案
Java的可移植性是基于.java檔案編譯后形成的唯一的位元組碼檔案.class檔案可以在不同作業系統上的jvm運行的機制,.class檔案是一組以8位位元組為基礎單位的二進制流,各個資料專案嚴格按照順序緊湊的排列在.class檔案中,中間沒有任何分隔符,
當程式員編譯了.java檔案后,在指定的路徑下會生成一個.class檔案,使用editplus可以直接以Hex viewer的格式打開.class檔案
ClassTest.java
package com.classloader;
public class ClassTest {
public static void main(String[] args) {
System.out.println("Hello,World!");
}
}
ClassTest.class
要想能讀懂class檔案這種生肉干貨,首先要理解.class檔案中的一些基本概念
基本概念
無符號數&表
無符號數是一種基本的資料型別,通常用u1,u2,u4,u8代表1、2、4、8個位元組的無符號數,
無符號數是用來描述數字、索引參考、數量值或者UTF-8編碼的字串值,可以稱作是.class檔案的基本組成單位
表是由多個無符號數或其他表構成的復合資料型別,整個.class檔案的本質就是一個表,(表大都習慣性的以_info結尾)
無論是無符號數,還是表,當需要描述同一型別但數量不定的多資料的時候,經常會使用一個位置的容量計數器加若干個連續的資料項的形式,
常量池
魔數(magic number) & 版本號
每個.class檔案的頭四個位元組被稱為“魔數”,其作用是確定該.class檔案是否為一個能被HOTSPOT虛擬機接收的.class檔案
魔數后面的四個位元組是版本號,第五和第六個位元組是“次版本號”,第七和第八個位元組是“主版本號”,
Java的版本號是從45開始的,自jdk1.1之后的每個jdk大版本發布的主版本號都向上+1,并且高版本的jdk能向下兼容以前版本的.class檔案,(注意:hex viewer下,.class檔案中的數字都是16進制數)
常量池
在主版本號后面的是常量池入口,常量池是.class檔案結構中與其他專案關聯最多的資料型別,也是占用.class檔案空間最大的資料專案之一,
由于常量池中的常量的數量是不固定的,所以常量池的入口需要放置一項u2型別的資料,代表常量池容量計數,這個容量計數是從1開始的(有別于傳統的程式員計數法則),
上圖中的.class檔案的常量池計數是34,由于從1開始,所以常量的個數是33(十六進制的22是十進制的34),也就是說,從計數位之后的33個表,都是表示常量的,
常量池中主要存放兩大類常量:字面量(literal)和字符參考(Symbolic References),
字面量比較接近于Java語言層的常量概念,例如文本字串、被宣告為final的常量值等等,
字符參考包括三類變數:類和介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符,
剛剛提到過,常量池中的每一個常量都是一個表,一共11種結構各不相同的表,這些表都有一個共同的特點,開始的第一位是一個u1型別的標志位,代表當前這個常量屬于哪種型別,(具體查看【查閱表格】)
總而言之,查看常量的方法就是:
1.第一個位元組為tag 查看常量池型別表找到對應的型別
2.找到對應結構的表,找到tag之后屬于常量的其他無符號數
訪問標志
常量池結束后,緊接著的兩個位元組表示訪問標志(access_flags),這個標志用來識別一些類或介面層次的訪問資訊,包括:這個classs是物體類還是介面;是否定義為public;是否是抽象類;是否為final類等等,
具體訪問標志的映射詳見【查閱表格】
類引索&父類引索&介面引索集合
類引索(this_class)和父類引索(super_class)都是一個u2型別的資料,介面引索集合是一組u2型別的資料集合,
在訪問標志之后,緊接著是類引索、父類引索,共占據4個位元組,他們各自指向一個型別為CONSTANT_Class_info的類描述符常量,通過CONSTANT_Class_info型別的常量中的索引值可以找到定義在CONSTANT_Utf8_info型別常量中的全限定名字串,
欄位表集合
在介面索引集合后的兩個位元組是fields_count型別,描述的是欄位表集合內有多少個欄位表,
欄位表對應的是程式員在.java檔案中的欄位,欄位表的各型別分別對應修飾詞、參考名稱等等
欄位表的閱讀方法和常量的閱讀方法一致,
欄位表結構以及欄位表中各結構型別詳見【查閱表格】
方法表集合
在欄位表集合結束后,接下來的兩個位元組是method_count型別,描述的是方法表集合中有多少個方法表,
方法表對應的是程式員在.java檔案中撰寫的方法,方法表的各型別分別對應修飾詞、參考名稱等等,
方法表結構以及方法表中各結構型別詳見【查閱表格】
屬性表集合
方法表集合之后四個位元組,描述的是屬性表集合,.class檔案有很多屬性,每個屬性,它的名稱需要從常量池中參考一個CONSTANT_Utf8_info型別的常量表示,
Code屬性
Java程式方法體內的代碼經過javac編譯處理之后,最終編程位元組碼指令存盤在Code屬性內,這之后就涉及到了位元組碼執行引擎的問題,之后會在其他的博客中進行講解,敬請期待,
在屬性表集合之后就是Code屬性,具體對應的型別詳見【查閱表格】
使用javap決議class檔案
對于.class檔案的決議作業,jdk為我們提供了類決議工具javap,javap生成的.class檔案決議比較直觀,容易理解,算是半生肉,結合上文講述的各個概念,應該不難理解,
具體使用方法是在cmd中輸入:
javap -verbose 類名
輸出結果大致是這樣:(以ClassTest.class為例)
查閱表格
常量池型別表
所有結構型別
訪問標志
欄位表結構
欄位表訪問標志
各標志的含義和其后半段的內容一致,表示欄位的修飾符
描述符標志字符含義
對于陣列型別,每一位都使用一個前置的“[”來描述,比如一個java.lang.String[][] 被記錄為 [[Ljava/lang/String
一個int[]被記錄為[I
方法表結構
方法訪問標志
Code 屬性
2020年01月14日 17時03分40秒
打賞一下:
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/2128.html
標籤:其他
下一篇:鏈表--鏈表中環的入口節點
