1. 程式人生 > >JVM八:class檔案結構(2)

JVM八:class檔案結構(2)

下面我們接著為訪問標誌,類索引,父類索引,介面索引集合,欄位集合,方法表集合

 訪問標誌:

常量池結束後緊接著的兩個位元組代表訪問標誌,用來標識一些類或介面的訪問資訊,包括:這個Class是類還是介面;是否定義為public;是否定義為abstract;如果是類的話,是否被宣告為final等。具體的標誌位以及含義如下表:

標誌名稱

標誌值

含義

ACC_PUBLIC

0x0001

是否是public

ACC_FINAL

0x0010

是否被宣告為final,只有類可以設定

ACC_SUPER

0x0020

是否允許使用invokespecial位元組碼指令的新語義,JDK1.0.2之後編譯出來的類的這個標誌預設為真

ACC_INTERFACE

0x0200

標識是一個介面

ACC_ABSTRACT

0x0400

是否是abstract,對於介面和抽象類來說為真,其他類都為假

ACC_SYNITHETIC

0x1000

標識這個類並非由使用者程式碼產生

ACC_ANNOTATION

0x2000

標識這是一個註解

ACC_ENUM

0x4000

標識這是一個列舉類

類索引(2個位元組)、父類索引(2個位元組)與介面索引(2個位元組介面數+介面)集合:

在訪問標誌access_flags後接下來就是類索引(this_class)和父類索引(super_class),這兩個資料都是u2型別的,而接下來的介面索引集合是一個u2型別的集合,class檔案由這三個資料項來確定類的繼承關係。由於Java中是單繼承,所以父類索引只有一個;但Java類可以實現多個介面,所以介面索引是一個集合。

類索引用來確定這個類的全限定名,這個全限定名就是說一個類的類名包含所有的包名,然後使用"/"代替"."。比如Object的全限定名是java.lang.Object。父類索引確定這個類的父類的全限定名,除了Object之外,所有的類都有父類,所以除了Object之外所有類的父類索引都不為0.介面索引集合儲存了implements語句後面按照從左到右的順序的介面

類索引和父類索引都是一個索引,這個索引指向常量池中的CONSTANT_Class_info型別的常量。然後再CONSTANT_Class_info常量中的索引就可以找到常量池中型別為CONSTANT_Utf8_info的常量,而這個常量儲存著類的全限定名。

欄位表集合:

      欄位表用來描述介面或類中宣告的變數。欄位包括類級變數和例項級變數,但不包括方法內變數。所謂的類級變數就是靜態變數,這個變數不屬於這個類的任何例項,可以不用定義類例項就可以使用;例項級變數不是靜態變數,是和類例項相關聯的,需要定義類例項才能使用。

那麼,宣告一個變數需要哪些資訊呢?有:欄位的作用域(public、private和protected修飾符)、是例項變數還是類變數(static修飾符)、可變性(final修飾符)、併發可見性(volatile修飾符)、是否可被序列化(transient修飾符)、欄位的資料型別(基本型別、物件、陣列)以及欄位名稱。包含的資訊有點多,不過不需要的可以不寫。這些資訊中,各個修飾符可以用布林值表示。而欄位叫什麼名字、欄位被定義為什麼型別資料都是無法固定的,只能用常量池中的常量來表示。下面是欄位表的格式:

型別

名稱

數量

U2

access_flags

1

U2

name_index

1

U2

descriptor_index

1

U2

attributes_count

1

attribute_info

attributes

attributes_count

其中的欄位修飾符access_flags,和類中的access_flags類似,對於欄位來說可以設定的標誌位及含義如下:

標誌名稱

標誌值

含義

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

顯然,ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED只能選擇一個,ACC_FINAL和ACC_VOLATILE不能同時選擇。介面中的欄位必須有ACC_PUBLIC、ACC_STATIC和ACC_FINAL標誌,這是Java語言本身的規則決定的。

access_flags給出了欄位中所有可以用布林值表示的修飾符,剩下的資訊就是欄位的名字、變數型別等資訊。access_flags後面的是name_index和descriptor_index,前者是欄位名的常量池索引,後者是欄位描述符的常量池索引。name_index可以描述欄位的名字,descriptor_index可以描述欄位的資料型別。不過,對於方法的描述符來說就要複雜一些,因為一個方法除了返回值型別,還有引數型別,而且引數的個數還不確定。根據描述符規則,這些型別都使用一個大寫字母來表示,如下表:

標識字元

含義

標識字元

含義

B

byte

J

long

C

char

S

short

D

double

Z

boolean

F

float

V

void

I

int

L

物件型別,如Ljava/lang/Object

對於陣列型別,每一個維度將使用一個前置的“[”字元來描述。比如定義一個java.lang.String[][]型別的二維陣列,將記錄為"[[Ljava/lang/String",一個double陣列"double[]"將標記為"[D"。

當描述符用來描述方法時,按照先引數列表,後返回值的順序描述,引數列表按照引數的嚴格順序放在一組小括號"()"內。比如方法void inc()的描述符是:()V。方法java.lang.String toString()的描述符是:()Ljava/lang/String。方法int indexOf(char[] source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromIndex)的描述符是:([CII[CIII)I。

descriptor_info後面是屬性資訊,這會在後面屬性表集合中介紹。

方法表集合:

   在欄位表集合中介紹了欄位的描述符和方法的描述符,對於理解方法表有很大幫助。class檔案儲存格式中對方法的描述和對欄位的描述幾乎相同,方法表的結構也和欄位表相同,這裡就不再列出。不過,方法表的訪問標誌和欄位的不同,列出如下:

 

標識名稱

標誌值

含義

ACC_PUBLIC

0x0001

方法是否是public

ACC_PRIVATE

0x0002

方法是否是private

ACC_PUBLICPROTECTED

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

方法是否是由編譯器自動產生的

下面我們來一個例子

JavaCode

public class Test{
	
	public int a;
	private String b;
	private int add(int arg1,int arg2){
		return arg1+arg2; 
	}
	
} 

二機制解析

Javap解析

C:\Users\GH\Desktop>javap -verbose Test.class
Classfile /C:/Users/GH/Desktop/Test.class
  Last modified 2018-8-8; size 287 bytes
  MD5 checksum 430dad91948d95b5532953a32356174c
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#16         // java/lang/Object."<init>":()V
   #2 = Class              #17            // Test
   #3 = Class              #18            // java/lang/Object
   #4 = Utf8               a
   #5 = Utf8               I
   #6 = Utf8               b
   #7 = Utf8               Ljava/lang/String;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               add
  #13 = Utf8               (II)I
  #14 = Utf8               SourceFile
  #15 = Utf8               Test.java
  #16 = NameAndType        #8:#9          // "<init>":()V
  #17 = Utf8               Test
  #18 = Utf8               java/lang/Object
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
}
SourceFile: "Test.java"

C:\Users\GH\Desktop>