Java Class 文件結構

Java語言只是我們瞭解編程的基礎語法,最終編譯成的Class文件纔是JVM解讀的二進制文件,瞭解Class文件結構,有助於理解編程原理。通過javap我們將瞭解Class內部結構。

Class文件是一組以8位字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內容幾乎全部是程序運行的必要數據, 沒有空隙存在。當遇到需要佔用8位字節以上空間的數據項時, 則會按照高位在前[1]的方式分割成若干個8位字節進行存儲。

根據Java虛擬機規範的規定,Class文件格式採用一種類似於C語言結構體的僞結構來存儲數據,這種僞結構中只有兩種數據類型:無符號數和表,後面的解析都要以這兩種數據類型爲基礎。

無符號數屬於基本的數據類型, 以u1、 u2、 u4、 u8來分別代表1個字節、 2個字節、 4個字節和8個字節的無符號數, 無符號數可以用來描述數字、 索引引用、 數量值或者按照UTF-8編碼構成字符串值。

表是由多個無符號數或者其他表作爲數據項構成的複合數據類型,所有表都習慣性地以“_info”結尾。表用於描述有層次關係的複合結構的數據,整個Class文件本質上就是一張表

《Java虛擬機規範》對這些表結構有詳細的定義,需要仔細研究。

Class文件格式結構

    u4 magic;  // 魔法數字,表明當前文件是.class文件,固定0xCAFEBABE
    u2 minor_version; // 分別爲Class文件的副版本和主版本
    u2 major_version; 
    u2 constant_pool_count; // 常量池計數
    cp_info constant_pool[constant_pool_count-1];  // 常量池內容
    u2 access_flags; // 類訪問標識
    u2 this_class; // 當前類
    u2 super_class; // 父類
    u2 interfaces_count; // 實現的接口數
    u2 interfaces[interfaces_count]; // 實現接口信息
    u2 fields_count; // 字段數量
    field_info fields[fields_count]; // 包含的字段信息 
    u2 methods_count; // 方法數量
    method_info methods[methods_count]; // 包含的方法信息
    u2 attributes_count;  // 屬性數量
    attribute_info attributes[attributes_count]; // 各種屬性

描述符

一個描述字段或方法的類型的字符串。

常量池

常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。字面量比較接近於Java語言層面的常量概念,如文本字符串、聲明爲final的常量值等。 而符號引用則屬於編譯原理方面的概念,包括了下面三類常量:

  • 類和接口的全限定名(Fully Qualified Name)
  • 字段的名稱和描述符(Descriptor)
  • 方法的名稱和描述符

格式: 

tag 表示常量項類型,整理如下:

常量池是最煩瑣的數據, 是因爲這14種常量類型各自均有自己的結構。

訪問標誌

在常量池結束之後,緊接着的兩個字節代表訪問標誌(access_flags),這個標誌用於識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義爲public類型;是否定義爲abstract類型;如果是類的話,是否被聲明爲final等。

類索引、 父類索引與接口索引集合

類索引( this_class) 和父類索引( super_class) 都是一個u2類型的數據,而接口索引集合( interfaces) 是一組u2類型的數據的集合,Class文件中由這三項數據來確定這個類的繼承關係。類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類的全限定名。由於Java語言不允許多重繼承,所以父類索引只有一個,除了java.lang.Object之外,所有的Java類都有父類,因此除了java.lang.Object外,所有Java類的父類索引都不爲0。接口索引集合就用來描述這個類實現了哪些接口,這些被實現的接口將按implements語句(如果這個類本身是一個接口,則應當是extends語句)後的接口順序從左到右排列在接口索引集合中。

字段表集合

字段表(field_info)用於描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量,但不包括在方法內部聲明的局部變量。

字段表集合中不會列出從超類或者父接口中繼承而來的字段,但有可能列出原本Java代碼之中不存在的字段,譬如在內部類中爲了保持對外部類的訪問性,會自動添加指向外部類實例的字段。另外,在Java語言中字段是無法重載的,兩個字段的數據類型、修飾符不管是否相同,都必須使用不一樣的名稱,但是對於字節碼來講,如果兩個字段的描述符不一致,那字段重名就是合法的。

格式:

方法表集合

Class文件存儲格式中對方法的描述與對字段的描述幾乎採用了完全一致的方式,方法表的結構如同字段表一樣,依次包括了訪問標誌(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項。

方法的定義可以通過訪問標誌、 名稱索引、 描述符索引表達清楚,但方法裏面的代碼去哪裏了?方法裏的Java代碼, 經過編譯器編譯成字節碼指令後,存放在方法屬性表集合中一個名爲“Code”的屬性裏面,屬性表作爲Class文件格式中最具擴展性的一種數據項目。

如果父類方法在子類中沒有被重寫(Override),方法表集合中就不會出現來自父類的方法信息。但同樣的, 有可能會出現由編譯器自動添加的方法, 最典型的便是類構造器“<clinit>”方法和實例構造器“< init>”方法。

在Java語言中,要重載(Overload)一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特徵簽名,特徵簽名就是一個方法中各個參數在常量池中的字段符號引用的集合,也就是因爲返回值不會包含在特徵簽名中,因此Java語言裏面是無法僅僅依靠返回值的不同來對一個已有方法進行重載的。但是在Class文件格式中,特徵簽名的範圍更大一些,只要描述符不是完全一致的兩個方法也可以共存。也就是說,如果兩個方法有相同的名稱和特徵簽名,但返回值不同,那麼也是可以合法共存於同一個Class文件中的。

格式:

屬性表集合

與Class文件中其他的數據項目要求嚴格的順序、長度和內容不同,屬性表集合的限制稍微寬鬆了一些,不再要求各個屬性表具有嚴格順序,並且只要不與已有屬性名重複,任何人實現的編譯器都可以向屬性表中寫入自己定義的屬性信息,Java虛擬機運行時會忽略掉它不認識的屬性。

格式:

屬性名稱 使用位置 含義
Code 方法表 Java代碼編譯成的字節碼指令
ConstantValue 字段表 final關鍵字定義的常量值
Deprecated 類、方法表、字段表 被聲明爲deprecated的方法和字段
Exceptions 方法表 方法拋出的異常
EnclosingMethod 類文件 僅當一個類爲局部類或者匿名類時才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法
InnerClasses 類文件 內部類列表
LineNumberTable Code屬性 Java源碼的行號與字節碼指令的對用關係
LocalVariableTable Code屬性 方法的局部變量描述
StackMapTable Code屬性 JDK1.6中新增的屬性,供新的類型檢查驗證器(Type Checker)檢查和處理目標方法的局部變量和操作數棧所需要的類型是否匹配
Signature 類、方法表、字段表 JDK1.5中新增的屬性,這個屬性用於支持泛型情況下的方法簽名,在Java語言中,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數化類型(Parameterized Types),則Signature屬性會爲他記錄泛型簽名信息。由於Java的泛型採用擦除法實現,在爲了避免類型信息被擦出後導致簽名混亂,需要這個屬性記錄泛型中的相關信息
SourceFile 類文件 記錄源文件名稱
SourceDebugExtension 類文件 JDK 1.6中新增的屬性,SourceDebugExtension屬性用於存儲額外的調試信息,譬如在進行JSP文件調試時,無法同構Java堆棧來定位到JSP文件的行號,JSR-45規範爲這些非Java語言編寫,卻需要編譯成字節碼並運行在Java虛擬機中的程序提供了一個進行調試的標準機制,使用SourceDebugExtension屬性就可以用於存儲這個標準所新加入的調試信息
Synthetic 類、方法表、字段表 標識方法或字段爲編譯器自動生成的
LocalVariableTypeTable JDK 1.5中新增的屬性,他使用特徵簽名代替描述符,是爲了引入泛型語法之後能描述泛型參數化類型而添加
RuntimeVisibleAnnotations 類、方法表、字段表 JDK 1.5中新增的屬性,爲動態註解提供支持。RuntimeVisibleAnnotations屬性用於指明哪些註解是運行時(實際上運行時就是進行反射調用)可見的
RuntimeInVisibleAnnotations 類、方法表、字段表 JDK 1.5新增的屬性,與RuntimeVisibleAnnotations屬性作用剛好相反,用於指明哪些註解是運行時不可見的
RuntimeVisibleParameter
Annotations
方法表 JDK 1.5新增的屬性,作用與RuntimeVisibleAnnotations屬性類似,只不過作用對象爲方法參數
RuntimeInVisibleAnnotations
Annotations
方法表 JDK 1.5中新增的屬性,作用與RuntimeInVisibleAnnotations屬性類似,只不過作用對象爲方法參數
AnnotationDefault 方法表 JDK 1.5中新增的屬性,用於記錄註解類元素的默認值
BootstrapMethods 類文件 JDK 1.7中新增的屬性,用於保存invokedynamic指令引用的引導方法限定符

javap介紹

用法:

 -help  --help  -?        輸出此用法消息
 -version                 版本信息,其實是當前javap所在jdk的版本信息,不是class在哪個jdk下生成的。
 -v  -verbose             輸出附加信息(包括行號、本地變量表,反彙編等詳細信息)
 -l                         輸出行號和本地變量表
 -public                    僅顯示公共類和成員
 -protected               顯示受保護的/公共類和成員
 -package                 顯示程序包/受保護的/公共類 和成員 (默認)
 -p  -private             顯示所有類和成員
 -c                       對代碼進行反彙編
 -s                       輸出內部類型簽名
 -sysinfo                 顯示正在處理的類的系統信息 (路徑, 大小, 日期, MD5 散列)
 -constants               顯示靜態最終常量
 -classpath <path>        指定查找用戶類文件的位置
 -bootclasspath <path>    覆蓋引導類文件的位置

$ javap -vTest

Classfile /C:/eclipse-workspace/mengying-xgj-english/ziguo-english-tools/target/classes/com/huaisu/generate/ftl/Test.class
  Last modified 2019-11-26; size 1559 bytes
  MD5 checksum 2a8f0d409a80b131f1caee022a18c8a4
  Compiled from "Test.java"
public class com.huaisu.generate.ftl.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/huaisu/generate/ftl/Test
   #2 = Utf8               com/huaisu/generate/ftl/Test
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/huaisu/generate/ftl/Test;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Class              #17            // com/huaisu/generate/ftl/Test$1
  #17 = Utf8               com/huaisu/generate/ftl/Test$1
  #18 = Methodref          #16.#9         // com/huaisu/generate/ftl/Test$1."<init>":()V
  #19 = Methodref          #16.#20        // com/huaisu/generate/ftl/Test$1.start:()V
  #20 = NameAndType        #21:#6         // start:()V
  #21 = Utf8               start
  #22 = Class              #23            // com/huaisu/generate/ftl/Test$2
  #23 = Utf8               com/huaisu/generate/ftl/Test$2
  #24 = Methodref          #22.#9         // com/huaisu/generate/ftl/Test$2."<init>":()V
  #25 = Methodref          #22.#20        // com/huaisu/generate/ftl/Test$2.start:()V
  #26 = Class              #27            // com/huaisu/generate/ftl/Test$3
  #27 = Utf8               com/huaisu/generate/ftl/Test$3
  #28 = Methodref          #26.#9         // com/huaisu/generate/ftl/Test$3."<init>":()V
  #29 = Methodref          #26.#20        // com/huaisu/generate/ftl/Test$3.start:()V
  #30 = Utf8               args
  #31 = Utf8               [Ljava/lang/String;
  #32 = Utf8               send
  #33 = Utf8               (Ljava/lang/String;)V
  #34 = Methodref          #35.#37        // java/lang/String.intern:()Ljava/lang/String;
  #35 = Class              #36            // java/lang/String
  #36 = Utf8               java/lang/String
  #37 = NameAndType        #38:#39        // intern:()Ljava/lang/String;
  #38 = Utf8               intern
  #39 = Utf8               ()Ljava/lang/String;
  #40 = Fieldref           #41.#43        // java/lang/System.out:Ljava/io/PrintStream;
  #41 = Class              #42            // java/lang/System
  #42 = Utf8               java/lang/System
  #43 = NameAndType        #44:#45        // out:Ljava/io/PrintStream;
  #44 = Utf8               out
  #45 = Utf8               Ljava/io/PrintStream;
  #46 = Class              #47            // java/lang/StringBuilder
  #47 = Utf8               java/lang/StringBuilder
  #48 = Methodref          #49.#51        // java/lang/Thread.currentThread:()Ljava/lang/Thread;
  #49 = Class              #50            // java/lang/Thread
  #50 = Utf8               java/lang/Thread
  #51 = NameAndType        #52:#53        // currentThread:()Ljava/lang/Thread;
  #52 = Utf8               currentThread
  #53 = Utf8               ()Ljava/lang/Thread;
  #54 = Methodref          #49.#55        // java/lang/Thread.getName:()Ljava/lang/String;
  #55 = NameAndType        #56:#39        // getName:()Ljava/lang/String;
  #56 = Utf8               getName
  #57 = Methodref          #35.#58        // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #58 = NameAndType        #59:#60        // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #59 = Utf8               valueOf
  #60 = Utf8               (Ljava/lang/Object;)Ljava/lang/String;
  #61 = Methodref          #46.#62        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #62 = NameAndType        #5:#33         // "<init>":(Ljava/lang/String;)V
  #63 = String             #64            // ,
  #64 = Utf8               ,
  #65 = Methodref          #46.#66        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #66 = NameAndType        #67:#68        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #67 = Utf8               append
  #68 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #69 = Methodref          #46.#70        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #70 = NameAndType        #71:#39        // toString:()Ljava/lang/String;
  #71 = Utf8               toString
  #72 = Methodref          #73.#75        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #73 = Class              #74            // java/io/PrintStream
  #74 = Utf8               java/io/PrintStream
  #75 = NameAndType        #76:#33        // println:(Ljava/lang/String;)V
  #76 = Utf8               println
  #77 = Long               5000l
  #79 = Methodref          #49.#80        // java/lang/Thread.sleep:(J)V
  #80 = NameAndType        #81:#82        // sleep:(J)V
  #81 = Utf8               sleep
  #82 = Utf8               (J)V
  #83 = Methodref          #84.#86        // java/lang/InterruptedException.printStackTrace:()V
  #84 = Class              #85            // java/lang/InterruptedException
  #85 = Utf8               java/lang/InterruptedException
  #86 = NameAndType        #87:#6         // printStackTrace:()V
  #87 = Utf8               printStackTrace
  #88 = Utf8               name
  #89 = Utf8               Ljava/lang/String;
  #90 = Utf8               e
  #91 = Utf8               Ljava/lang/InterruptedException;
  #92 = Utf8               StackMapTable
  #93 = Class              #94            // java/lang/Throwable
  #94 = Utf8               java/lang/Throwable
  #95 = Utf8               SourceFile
  #96 = Utf8               Test.java
  #97 = Utf8               InnerClasses
{
  public com.huaisu.generate.ftl.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/huaisu/generate/ftl/Test;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #16                 // class com/huaisu/generate/ftl/Test$1
         3: dup
         4: invokespecial #18                 // Method com/huaisu/generate/ftl/Test$1."<init>":()V
         7: invokevirtual #19                 // Method com/huaisu/generate/ftl/Test$1.start:()V
        10: new           #22                 // class com/huaisu/generate/ftl/Test$2
        13: dup
        14: invokespecial #24                 // Method com/huaisu/generate/ftl/Test$2."<init>":()V
        17: invokevirtual #25                 // Method com/huaisu/generate/ftl/Test$2.start:()V
        20: new           #26                 // class com/huaisu/generate/ftl/Test$3
        23: dup
        24: invokespecial #28                 // Method com/huaisu/generate/ftl/Test$3."<init>":()V
        27: invokevirtual #29                 // Method com/huaisu/generate/ftl/Test$3.start:()V
        30: return
      LineNumberTable:
        line 6: 0
        line 10: 7
        line 11: 10
        line 15: 17
        line 16: 20
        line 20: 27
        line 21: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  args   [Ljava/lang/String;

  public static void send(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=3, args_size=1
         0: aload_0
         1: invokevirtual #34                 // Method java/lang/String.intern:()Ljava/lang/String;
         4: dup
         5: astore_1
         6: monitorenter
         7: getstatic     #40                 // Field java/lang/System.out:Ljava/io/PrintStream;
        10: new           #46                 // class java/lang/StringBuilder
        13: dup
        14: invokestatic  #48                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        17: invokevirtual #54                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        20: invokestatic  #57                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        23: invokespecial #61                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        26: ldc           #63                 // String ,
        28: invokevirtual #65                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        31: aload_0
        32: invokevirtual #65                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        35: invokevirtual #69                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        38: invokevirtual #72                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        41: ldc2_w        #77                 // long 5000l
        44: invokestatic  #79                 // Method java/lang/Thread.sleep:(J)V
        47: goto          55
        50: astore_2
        51: aload_2
        52: invokevirtual #83                 // Method java/lang/InterruptedException.printStackTrace:()V
        55: aload_1
        56: monitorexit
        57: goto          63
        60: aload_1
        61: monitorexit
        62: athrow
        63: return
      Exception table:
         from    to  target type
            41    47    50   Class java/lang/InterruptedException
             7    57    60   any
            60    62    60   any
      LineNumberTable:
        line 24: 0
        line 25: 7
        line 27: 41
        line 28: 47
        line 29: 51
        line 24: 55
        line 32: 63
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      64     0  name   Ljava/lang/String;
           51       4     2     e   Ljava/lang/InterruptedException;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 50
          locals = [ class java/lang/String, class java/lang/String ]
          stack = [ class java/lang/InterruptedException ]
        frame_type = 4 /* same */
        frame_type = 68 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 2
}
SourceFile: "Test.java"
InnerClasses:
     #16; //class com/huaisu/generate/ftl/Test$1
     #22; //class com/huaisu/generate/ftl/Test$2
     #26; //class com/huaisu/generate/ftl/Test$3

內部類字節碼,理解內部類

public class InnerClassTest {
	int field1 = 1;
	private int field2 = 2;

	public InnerClassTest() {
		InnerClassA inner = new InnerClassA();
		int v = inner.x2;
	}

	public class InnerClassA {
		int x1 = field1;
		private int x2 = field2;
	}

	public static void main(String[] args) {
		InnerClassTest outerObj = new InnerClassTest();
		// 不在外部類內部,使用:外部類對象. new 內部類構造器(); 的方式創建內部類對象
		// InnerClassA innerObj = outerObj.new InnerClassA();
	}
}

javap -c InnerClassTest.class 

C:\java\jdk1.8.0\bin\javap -c InnerClassTest.class
Compiled from "InnerClassTest.java"
public class com.huaisu.generate.ftl.InnerClassTest {
  int field1;

  public com.huaisu.generate.ftl.InnerClassTest();
    Code:
       0: aload_0
       1: invokespecial #11                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_1
       6: putfield      #13                 // Field field1:I
       9: aload_0
      10: iconst_2
      11: putfield      #15                 // Field field2:I
      14: new           #17                 // class com/huaisu/generate/ftl/InnerClassTest$InnerClassA
      17: dup
      18: aload_0
      19: invokespecial #19                 // Method com/huaisu/generate/ftl/InnerClassTest$InnerClassA."<init>":(Lcom/huaisu/generate/ftl/InnerClassTest;)V
      22: astore_1
      23: aload_1
      24: invokestatic  #22                 // Method com/huaisu/generate/ftl/InnerClassTest$InnerClassA.access$0:(Lcom/huaisu/generate/ftl/InnerClassTest$InnerClassA;)I
      27: istore_2
      28: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #1                  // class com/huaisu/generate/ftl/InnerClassTest
       3: dup
       4: invokespecial #35                 // Method "<init>":()V
       7: astore_1
       8: return

  static int access$0(com.huaisu.generate.ftl.InnerClassTest);
    Code:
       0: aload_0
       1: getfield      #15                 // Field field2:I
       4: ireturn
}

 javap -c InnerClassTest$InnerClassA.class

Compiled from "InnerClassTest.java"
public class com.huaisu.generate.ftl.InnerClassTest$InnerClassA {
  int x1;

  final com.huaisu.generate.ftl.InnerClassTest this$0;

  public com.huaisu.generate.ftl.InnerClassTest$InnerClassA(com.huaisu.generate.ftl.InnerClassTest);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #13                 // Field this$0:Lcom/huaisu/generate/ftl/InnerClassTest;
       5: aload_0
       6: invokespecial #15                 // Method java/lang/Object."<init>":()V
       9: aload_0
      10: aload_1
      11: getfield      #18                 // Field com/huaisu/generate/ftl/InnerClassTest.field1:I
      14: putfield      #23                 // Field x1:I
      17: aload_0
      18: aload_1
      19: invokestatic  #25                 // Method com/huaisu/generate/ftl/InnerClassTest.access$0:(Lcom/huaisu/generate/ftl/InnerClassTest;)I
      22: putfield      #29                 // Field x2:I
      25: return

  static int access$0(com.huaisu.generate.ftl.InnerClassTest$InnerClassA);
    Code:
       0: aload_0
       1: getfield      #29                 // Field x2:I
       4: ireturn
}

編譯器給內部類提供了一個接受 InnerClassTest 類型對象(即外部類對象)的構造方法,內部類本身還定義了一個名爲 this$0 的 InnerClassTest 類型的引用,這個引用在構造方法中指向了參數所對應的外部類對象。

內部類的構造方法通過 invokestatic 指令執行外部類的 access$100 靜態方法得到外部類對象的 field2 字段的值,並且在後面賦值給 x2 字段。這樣的話內部類就成功的通過外部類提供的靜態方法得到了對應外部類對象的 field2 。

在非靜態內部類訪問外部類私有成員 或 外部類訪問內部類私有成員的時候,對應的外部類生成一個靜態方法,用來返回對應私有成員的值,而對應外部類對象或內部類對象通過調用其內部類或外部類提供的靜態方法來獲取對應的私有成員的值。

靜態內部類沒有指向外部類對象的引用,編譯器只爲這個靜態內部類提供了一個無參構造方法。
而且因爲外部類對象需要訪問當前類的私有成員,編譯器給這個靜態內部類生成了一個名爲 access$000 的靜態方法。

內存泄露

如果一個外部類的實例對象的方法返回了一個內部類的實例對象,這個內部類對象被長期引用了,即使那個外部類實例對象不再被使用,但由於內部類持有外部類的實例對象,這個外部類對象將不會被垃圾回收,這也會造成內存泄露。

通過一系列的稱爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。

在Java語言中, 可作爲GC Roots的對象包括下面幾種:

  • 虛擬機棧( 棧幀中的本地變量表) 中引用的對象。
  • 方法區中類靜態屬性引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI( 即一般說的Native方法) 引用的對象。

判定一個常量是否是“廢棄常量”比較簡單, 而要判定一個類是否是“無用的類”的條件則相對苛刻許多。 類需要同時滿足下面3個條件才能算是“無用的類”:該類所有的實例都已經被回收, 也就是Java堆中不存在該類的任何實例。加載該類的ClassLoader已經被回收。該類對應的java.lang.Class對象沒有在任何地方被引用, 無法在任何地方通過反射訪問該類的方法。

在大量使用反射、 動態代理、 CGLib等ByteCode框架、 動態生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能, 以保證永久代不會溢出。

 

參考:

JVM指令集整理

Java虛擬機運行時數據區

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章