JVM Class詳解之一

ahjdzx1990 8年前發布 | 9K 次閱讀 JVM Java開發

來自: https://yq.aliyun.com/articles/7241

首先看Class中包含哪些信息簡單的說所有java文件中有的信息class文件都有,編譯器幫我們將java文件轉化成了JVM能看懂的class格式而已

Class 概述

Class文件是一組以8位字節為基礎的二進制流,各個數據項目按照嚴格順序緊湊排列在Class文件中。所有的16位,32位,64位長度的數據將被構造成2個,4個,8個字節單位來標示。

ClassFile結構

類型 名稱 數量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count-1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fields fields_count
u2 methods_count 1
method_info methods methods_count
u2 attributes_count 1
attribute_info attributes attributes_count

class格式說明

  • magic:魔數,魔數的唯一作用是確定這個文件是否為一個能被虛擬機所接受的Class文件。魔數值固定為0xCAFEBABE
  • minor_version、major_version: 分別為Class文件的副版本和主版本
  • constant_pool_count: 常量池計數器,constant_pool_count的值等于constant_pool表中的成員數加1
  • constant_pool[]: 常量池,constant_pool是一種表結構,它包含Class文件結構及其子結構中引用的所有字符串常量、類或接口名、字段名和其它常量。常量池不同于其他,索引從1開始到constant_pool_count -1
  • access_flags: 訪問標志,access_flags是一種掩碼標志,用于表示某個類或者接口的訪問權限及基礎屬性
  • this_class: 類索引,this_class的值必須是對constant_pool表中項目的一個有效索引值
  • super_class: 父類索引
  • interfaces_count: 接口計數器,interfaces_count的值表示當前類或接口的直接父接口數量
  • interfaces[]: 接口表,interfaces[]數組中的每個成員的值必須是一個對constant_pool表中項目的一個有效索引值,它的長度為interfaces_count
  • fields_count: 字段計數器
  • fields[]: 字段表,fields[]數組中的每個成員都必須是一個fields_info結構的數據項
  • methods_count: 方法計數器
  • methods[]: 方法表,methods[]數組中的每個成員都必須是一個method_info結構的數據項
  • attributes_count: 屬性計數器
  • attributes[]: 屬性表,attributes表的每個項的值必須是attribute_info結構

廢話不多說HelloWorld搞起

public class HelloWorld
{
String str = "";

public String getStr() { return str; }

public void setStr(String str) { this.str = str; } }</code></pre>

編譯成class文件以后,只用javap -verbose HelloWorld.class 指令可以查看當前class的內容

同時使用UE打開class文件

我們來一起看下ClassFile結構

前4個字節為魔數,也就是0xCAFEBABE,這里都是十六進制

魔數后2個字節為副版本號,這里副版本號是0

主版本號0x0033,轉為十進制,主版本號是51 標示當前class是通過jdk 1.7編譯的,0x32是jdk1.6 0x31是jdk1.5

這兩個字節是常量池計數器,常量池的數量為0x001A,轉為十進制是26,也就是說常量池索引為1~26

</div>

從常量池開始

常量池計數器后面緊跟著就是常量池的內容所有的常量池項都具有如下通用格式:

cp_info 
{ 
u1 tag; 
u1 info[]; 
}
CONSTANT_Class_info 
{ 
u1 tag; 
u2 name_index; 
}
CONSTANT_Fieldref_info 
{ 
u1 tag; 
u2 class_index; 
u2 name_and_type_index; 
}
CONSTANT_Methodref_info 
{ 
u1 tag; 
u2 class_index; 
u2 name_and_type_index; 
}
CONSTANT_InterfaceMethodref_info 
{ 
u1 tag; 
u2 class_index; 
u2 name_and_type_index; 
}
CONSTANT_NameAndType_info 
{ 
u1 tag; 
u2 name_index; 
u2 descriptor_index; 
}
CONSTANT_Utf8_info 
{ 
u1 tag; 
u2 length; 
u1 bytes[length]; 
}
CONSTANT_MethodHandle_info 
{
u1 tag; 
u1 reference_kind; 
u2 reference_index; 
}

后面的0x07對應tag找到是CONSTANT_Class,標示接下來的是一個class的信息。后面的 00 02 是class的name_index 標示指向常量池的第二個常量。我們再看第二個常量

第二個常量是01開頭,我們查看常量類型表中對應是Utf-8,再按照utf-8的結構,后面的00 0A代表了這個utf-8的長度這里長度轉換為10進制是11,后面緊跟著utf-8的實際內容

再后面0x 07,是常量池的下一個產量,也是一個class信息,后面跟00 04,name_index執行常量池的

第4個常量。

第4個常量又是utf-8,后面長度為 0x10 十進制為16,接下來的為實際內容

接下來都可以按照此方法分析。

直觀結果可以通過javap指令查看

常量池后面緊跟的2個字節是Access Flag,這個表示用于標示類或接口層次的訪問信息,如這個Class是類還是接口,是否為public類型,是否定義為abstrace類型。

標志名稱 標志值 含義

ACC_PUBLIC 0x0001 是否為public類型

ACC_FINAL 0x0010 是否被聲明為final,只有類可設置

ACC_SUPER 0x0020 是否允許使用invokespecial字節碼指令,JDK1.2以后編譯出來的類這個標志為真

ACC_INTERFACE 0x0200 標識這是一個接口

ACC_ABSTRACT 0x0400 是否為abstract類型,對于接口和抽象類,此標志為真,其它類為假

ACC_SYNTHETIC 0x1000 標識別這個類并非由用戶代碼產生

ACC_ANNOTATION 0x2000 標識這是一個注解

ACC_ENUM 0x4000 標識這是一個枚舉

我們這里0021標示為public Class

接下來的是類索引,父類索引與接口索引集合this_class,super_class,interfaces_count,interfaces

類索引

為2個字節

這里為00 01,指向常量池中第一個常量,之前我們分析過常量池中第一個常量為Class類型,內容指向第二個常量UTF-8的HelloWorld。

標示當前名為HelloWorld

父類索引

也是2個字節

指向常量中第三個常量,對應內容為java/lang/Object

接口數量

表示接口數量為0

字段表集合

00 01 標示字段數量為1字段表的格式如下

類型 名稱 數量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_coun

accessFlags為 00 00 當時當前字段無修飾符字段修飾符格式如下

標志名稱 標志值 含義
ACC_PUBLIC 0x0001 字段是否為public
ACC_PRIVATE 0x0002 字段是否為private
ACC_PROTECTED 0x0004 字段是否為protected
ACC_STATIC 0x0008 字段是否為static
ACC_FINAL 0x0010 字段是否為final
ACC_VOLATILE 0x0040 字段是否為volatile
ACC_TRANSIENT 0x0080 字段是否為transient
ACC_SYNTHETIC 0x1000 字段是否為編譯器自動產生
ACC_ENUM 0x4000 字段是否為enum

name_index為 00 05指向常量池中的第五個常量

第5個常量為str,變量名為str

descriptor_index指向常量池第6個變量,為Ljava/lang/String類型

attributes_count(屬性計數器,占2字節,0x0000,所以該字段沒有額外需要描述的信息)

方法集合

method_count: 00 03 有3個方法

methods:方法表集合

類型 名稱 數量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_coun

access flags的定義見下表

標志名稱 標志值 含義
ACC_PUBLIC 0x0001 字段是否為public
ACC_PRIVATE 0x0002 字段是否為private
ACC_PROTECTED 0x0004 字段是否為protected
ACC_STATIC 0x0008 字段是否為static
ACC_FINAL 0x0010 字段是否為final
ACC_SYNCHRONIZED 0x0020 字段是否為synchronized
ACC_BRIDGE 0x0040 方法是否是由編譯器產生的橋接方法
ACC_VARARGS 0x0080 方法是否接受不定參數
ACC_NATIVE 0x0100 字段是否為native
ACC_ABSTRACT 0x0400 字段是否為abstract
ACC_STRICTFP 0x0800 字段是否為strictfp
ACC_SYNTHETIC 0x1000 字段是否為編譯器自動產生

這里方法access flags 為 00 01 說明方法為public的

name_index為00 07,方法名指向常量中第7個常量方法名為, descriptor_index為常量池第8個常量()V

attributes_count 為 00 01標示這個方法的屬性表集合中有一個屬性。屬性名稱為接下來2位0x0009,指向常量池中第9個常量:Code

接下來4位為 00 00 00 3D標示Code屬性值的字節長度為3D,接下來為00 02標示該方法的操作數棧的深度最大值為2.

00 01標示該方法的局部變量占用空間為1.

接下來4位00 00 00 0B 為機器編譯生成字節碼指令的長度為11,后面11個字節就是字節碼指令(字節碼指令可查詢虛擬機字節碼指令表),這里字節碼指令長度用4個字節標示,所有字節碼指令超長Class編譯會失敗的。

再接下來為 00 00標示Code屬性異常表結合為空。

再后面為 00 02,,說明Code帶有2個屬性, 00 10即為Code屬性第一個屬性的屬性名成指向常量池中第16個常量

接下來的00 00 00 0E 標示LinueNumberTable屬性值所占字節長度為15.接下來2位 00 03標示該line number table中有3個line number table表,start pc為 00 00 line number第 00 01個為00 04 第 00 02個為 00 0A 

再后面的 00 01又是第二個方法的access flags,接著開始第二個方法。

聲明:云棲社區站內文章,未經作者本人允許或特別聲明,嚴禁轉載,但歡迎分享。

</code></code></div>

 本文由用戶 ahjdzx1990 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!