Class文件結(jié)構(gòu)3之字段表與方法表
1、字段表
字段表緊隨在接口表索引之后,字段表包含訪問(wèn)標(biāo)記、字段名索引、描述符索引、屬性表,其中屬性表包含屬性計(jì)數(shù)器與屬性集合
以這段代碼為例:
- package com.yang.testField;
- public class Main {
- private volatile int a = 1;
- public static final String b = "abc";
- }
16進(jìn)制數(shù)據(jù)如下圖所示:

可以看得出,字段計(jì)數(shù)為0x0002,因?yàn)橛?個(gè)字段,a和b。
字段a的訪問(wèn)標(biāo)記是是0x0042,用這個(gè)值與標(biāo)識(shí)符的特征值取與,如果結(jié)果為1,則表示該字段擁有相應(yīng)的標(biāo)識(shí)符。字段標(biāo)識(shí)符如下所示:

這里我們可以得出,a的訪問(wèn)標(biāo)記有ACC_PRIVATE與ACC_VOLATILE。
a的名稱索引為0x0005,我們看一下常量池:

可以得出第一個(gè)字段的名稱索引指向常量池中第5個(gè)常量項(xiàng),即“a”。
a的描述符索引為0x0006,即常量池中的“I”,完成的字段類型與描述符的對(duì)照表如下:

接下來(lái)是a的屬性計(jì)數(shù)器,對(duì)應(yīng)的值為0x0000,代表a沒(méi)有屬性表。
貼一下b字段表中的屬性表:

b的屬性計(jì)數(shù)器為0x0001,代表著有屬性表,屬性表中只有一個(gè)元素,為0x0009,常量池中顯示為ConstantValue,說(shuō)明
該屬性是ConstantValue類型的,屬性長(zhǎng)度為2,屬性值索引為0x000A,即找到常量池中的#11,再找到#21,原來(lái)是個(gè)字符串"abc"。
為什么int a沒(méi)有屬性表,而static final b卻有屬性表?這要從字段的賦值策略說(shuō)起:
對(duì)于一個(gè)實(shí)例字段,比如這里的a,賦值階段發(fā)生在對(duì)象實(shí)例的構(gòu)造方法中,即
對(duì)于一個(gè)非final的靜態(tài)字段,賦初始值會(huì)發(fā)生在解析階段,而賦用戶指定的值,會(huì)發(fā)生在初始化階段,在類構(gòu)造器方法中完成,即
對(duì)于一個(gè)final的靜態(tài)字段,且是基本類型或者是String類型,在編譯期間就給該變量賦予用戶指定的值,并在常量池中形成一個(gè)ConstantValue類型的屬性,屬性值就是常量的值。如果是除去String類型以外的引用類型,那么就是在初始化階段完成賦值操作。
下面以一個(gè)例子說(shuō)明:
- package com.yang.testField;
- public class Main {
- private volatile int a = 1;
- public static final String b = "abc";
- public static String c="def";
- public static Thread d=new Thread();
- }

這里面完成的是對(duì)實(shí)例變量的賦值操作。

這里面完成的是對(duì)普通靜態(tài)變量c與非String的引用類型變量d的賦值操作。
更多關(guān)于對(duì)
2、方法表
緊接著字段表的是方法表,方法表和字段表類似,方法表包含方法計(jì)數(shù)、訪問(wèn)標(biāo)記、名稱索引、描述符索引、屬性表,其中屬性表也是包含屬性計(jì)數(shù)與屬性集合。
方法計(jì)數(shù)、名稱索引這邊就不再說(shuō)明了。
方法的訪問(wèn)標(biāo)記有:

這里有一個(gè)簡(jiǎn)單的例子:
- package com.yang.testMethod;
- public class Main {
- public Main() {
- }
- private int getInt(int k) {
- return k;
- }
- public static Thread getThread(int i, double d, Runnable runnable) {
- System.out.println(i * d);
- return new Thread(runnable);
- }
- }
構(gòu)造方法的描述符為()V
getInt方法的描述符為(I)I
getThread方法的描述符為(IDLjava/lang/Runnable;)Ljava/lang/Thread;
從這里,我們可以看得出,方法描述符的組織方式是這樣子的:(參數(shù)列表內(nèi)字段的描述符)返回值的描述符
接下來(lái)討論方法的屬性表,前面說(shuō)過(guò)了,屬性表包含屬性計(jì)數(shù)與屬性集合,屬性集合又包含屬性名稱索引+屬性長(zhǎng)度+屬性值。
屬性表內(nèi)最主要的屬性就是Code屬性了,Code屬性內(nèi)有幾個(gè)比較重要的東西:字節(jié)碼、LineNumberTable行號(hào)表、LocalVariableTable局部變量表、ExceptionTable異常表
用一下的代碼為例:
- public static Thread getThread(int i, double d, Runnable runnable) {
- try {
- System.out.println(i * d);
- }catch (Exception e){
- return null;
- }
- return new Thread(runnable);
- }
字節(jié)碼是class文件中最重要的東西了,jvm主要就是抽取字節(jié)碼,然后去執(zhí)行。

LineNumberTable內(nèi)維護(hù)著java源碼與字節(jié)碼之間的對(duì)應(yīng)關(guān)系:

LocalVariableTable內(nèi)記錄著局部變量描述:

關(guān)于局部變量表的詳細(xì)內(nèi)容,可以參考我的另外一篇文章虛擬機(jī)棧的五臟六腑 。
ExceptionTable會(huì)告訴虛擬機(jī)異常的處理邏輯,比如下圖的異常表,說(shuō)明如果字節(jié)碼從第0行到第10行出現(xiàn)了type類型的異常,那么將會(huì)跳轉(zhuǎn)到第13行的字節(jié)碼進(jìn)行處理。
