JVM學習小結(1)-Class類文件結構

Class類文件的結構

概念:Class文件是一組以8位字節爲基礎單位的二進制流
按順序整齊排列
沒有任何分隔符,內容全部是運行時的必要數據,沒有空隙存在

  • 排序方式:高位在前
    Big-Endian:最高字節在地址最低位,最低字節在地址最高位
    Little-Endian:相反

  • 儲存方式:類似於C語言結構體的僞結構來儲存只有兩種數據類型
    無符號:屬於基本數據類型

    u1:1個字節
    u2:2個字節
    u4:4個字節
    u8:8個字節

  • 表:多個無符號數或其他表作爲數據項構成的複合數據類型
    以"_info"結尾
    描述有層次關係的複合結構數據
    整個Class文件本質上就是一張表

  • 魔數: 魔數
    每個Class文件的頭4個字節
    確定這個文件是不是一個能被虛擬機接收的Class文件
    Java中的值爲:0xCAFEBABE->咖啡寶貝

  • Class文件版本:
    緊接着的4個字節
    第5個和第6個:次版本號(Minor Version)
    第7個和第8個:主版本號(Major Version)
    滿足向下兼容,即高版本兼容低版本的Class文件,反之不行

  • 常量池:
    緊接着版本號
    Class文件中的資源倉庫,佔用空間最大
    常量池容量計數值(constant_pool_count),從1開始而不是從0開始計數
    常量池中常量數量不確定,需要一個計數是u2類型的數據
    設計者將從0位做特殊考慮,特殊情況下,有些指向常量池的索引數據要表達"不引用任何常量池項目"的意義
    常量池容量(偏移地址:0x00000008),十進制22,表示常量池中有21項常量,索引1~21
    主要存放數據類型字面量(Literal):
    如文本字符串,final常量等符號引用(Symbolic References)
    類和接口的全限定名
    字段的名稱和描述符
    方法的名稱和描述符

  • 14種常量池的每個常量表:
    constant_utf8_info 1 utf-8
    u1 tag 1 標誌位//用於區分常量類型
    u2 length 1 utf-8真正的字符串內容
    constant_integer_info 3 整型
    constant_float_info 4 浮點型
    constant_long_info 5 長整型
    constant_double_info 6 雙精度
    constant_class_info 7 類或接口的符號引用
    constant_string_info 8 字符串型
    constant_fieldref_info 9 字段符號引用
    constant_methodref_info 10 類中方法的符號引用
    constant_interfaceMethodref_info 11 接口中符號引用
    constant_NameAndType_info 12 字段或方法的部分符號引用
    constant_MethodHandle_info 15 方法句柄
    constant_MethodType_info 16 表示方法類型
    constant_InvokeDynamic_info 18 動態方法調用點

  • 訪問標識:緊接着常量池,兩個字節,總共16位,當前只定義了其中8個,未使用一律使用0
    ACC_PUBLIC 0x0001 public
    ACC_FINAL 0x0010 final
    ACC_SUPER 0x0020 invokespecial
    ACC_INTERFACE 0x0400 abstract
    ACC_SYNTHETIC 0x1000 並非由用戶代碼產生
    ACC_ANNOTATION 0x2000 註解
    ACC_ENUM 0x4000 枚舉

  • 類索引(this_class),父類索引(super_class)與接口索引(interfaces)集合(u2類型數據)
    緊接着訪問標誌
    u2類型(this_class,super_class)u2類型集合(interfaces)
    各索引的含義:
    這三項數據來確定類的繼承關係
    類索引(this_class)用來確定類全限定名
    父類索引(super_class)確定父類的全限定名
    接口索引描述類實現的接口,按從左到右的順序排列
    父類索引(super_class)除了java.lang.Object外,其他類都不爲0

  • 字段集合(field_info)
    描述接口/類中聲明的變量包含:
    作用域(public private protected)
    實例變量還是類變量(static)
    可變性(final)
    併發可見性(volatile)
    序列化(transient)
    字段數據類型
    字段名稱
    全限定名,相當於類的全路徑,只是把.換成/,如java/lang/Object
    簡單名稱,方法或屬性名的簡寫,如inc()方法和m屬性,則簡單名稱爲inc和m
    描述符:
    描述字段數據類型,方法的參數列表和返回值
    基本數據類型和void都用一個大寫字母來表示,對象類型用L加全限定名來表示
    標識名: B(byte), C(char), D(double), F(float), I(int), J(long), S(short), Z(boolean), V(void), L(對象類型)
    數組描述符:
    一維數組:String[]–>[Ljava/lang/String
    二維數組:String[][]–>[[Ljava/lang/String
    方法描述符:
    參數列表在前,返回值在後的順序參數列表放在()內,如:void inc()描述爲()V
    int indexOf(char[] source,int sourceOffset, int sourceCount,char[] target, int targetOffset, int targetCount,int fromIndex)描述爲 ([CII[CII)I

  • 方法表集合:
    包含:
    訪問標誌(access_flags)
    名稱索引(name_index)
    描述符索引(descriptor_index)
    屬性表集合(attributes)
    方法表集合中沒有方法裏面的代碼,Java代碼存在屬性表中名爲"Code"的屬性裏面

  • 重寫和重載的原理:
    Override:如果子類未重寫父類方法,方法表集合中就不會出現來自父類的方法信息,但是有可能出現編譯器自動添加方法,如構造方法
    OverLoad:重載方法,保證和原方法具有相同的簡單名稱之外,還必須擁有一個與原方法不同的特徵簽名,返回值沒有包含在特徵簽名中,因此無法依靠返回值確定方法重載,在Class文件中,如果一個方法具有相同的名稱和特徵簽名,但返回值不同,可以包含在同一個Class文件中
    特徵簽名:一個方法中各個參數在常量池中的字段符號引用的集合

  • 屬性表集合:
    描述某些場景的專有信息, 沒有太多順序長度內容的限制
    虛擬機規範預定義的屬性
    屬性名及使用位置及含義:
    Code 方法表 Java代碼編譯成的字節碼文件
    ConstantValue 字段表 final關鍵字定義的常量
    Deprecated 類方法表字段表 被聲明爲deprecated的方法和字段
    Exceptions 方法表 方法拋出異常
    EnclosingMethod 類文件 一個類爲匿名類,局部類擁有此屬性,用於標識這個類所在的外圍方法
    InnerClass 類文件 內部類列表
    LineNumber Code屬性 Java源代碼的行號與字節碼指令的對應關係
    LocalVariableTable Code屬性 方法的局部變量描述
    StackMapTable Code屬性 JDK1.6新增,類型檢查,處理目標方法的局部變量和操作數棧所需的類型是否匹配
    Signature 類方法表字段表 JDK1.5新增,支持泛型情況下方法簽名
    SourceFile 類文件 記錄源文件名
    SourceDebugExtension 類文件 JDK1.6增,用於存儲額外的調試信息
    Synthetic 類方法表字段表 標識方法或字段爲編譯器自動生成的
    LocalVariableTypeTable JDK1.5新增,使用特徵簽名代替描述符
    RuntimeVisibleAnnotations 類方法表字段表 JDK1.5新增,爲動態註解提供支持,指明哪些註解是運行可見
    BootstrapMethods JDK1.7新增,用於保存invokedynamic指令引用的引導方法限定符
    Code屬性:
    Java編譯器處理後的字節碼指令存儲在Code屬性內; Code屬性出現在方法表的屬性集合中; 並非所有的方法都必須存在這個屬性
    類型 名稱 數量
    u2 attribute_name_index(指向CONSTANT_UF8_info類型常量的索引) 1
    u4 attribute_length(屬性值長度) 1
    u2 max_stack(操作數佔最大深度,在執行操作數棧時都不會超過這個最大深度,JVM在需要的時候根據此值分配棧幀中的操作棧深度) 1
    u2 max_locals(局部變量表所需的儲存空間根據Slot//局部變量最小單位,計算) 1
    u4 code_length(編譯後的字節碼指令) 1
    u1 code(編譯後的字節碼指令流) code_length
    u2 exception_table_length(異常表長度) 1
    exception_info exception_table(異常表內容) exception_table_length
    u2 attributes_counts(屬性數量) 1
    attribute_info attributes(屬性內容) attributes_counts
    Code屬性是Class文件中最重要的一個屬性,如果把Class文件分解爲Code和Metadata,Code可以描述爲代碼,Metadata解釋其他數據
    attribute_name_index:固定爲"Code",表示該屬性的名字
    attribute_length:由於屬性名和屬性長度一共6個字節(u2+u4),所以屬性值的長度就是整個屬性表的長度減去這兩個屬性
    max_stack:在方法執行的任意時刻,操作數棧都不會超過這個長度,虛擬機運行時根據這個值分配棧幀中的操作棧深度
    max_locals:單位Solt,是虛擬機爲局部變量分配內存的最小單位; 注:Slot重用,並不是方法中使用多少局部變量,就把這些局部變量所佔的Slot之和作爲max_locals.當代碼執行超出一個局部變量的作用域時,這個局部變量所佔的Slot可以被其他局部變量所使用
    code_length:code_length代表編譯後的字節碼長度,code是儲存字節碼指令的一系列字節流
    exception_table_length:不是必須存在的

  • this關鍵字:
    任何實例方法中都能通過this訪問所屬對象
    javac編譯器編譯的時候把對this關鍵字訪問轉變爲一個對普通方法參數的訪問然後在虛擬機調用實例方法時自動傳入此參數,所以在實例方法的局部變量表中至少會存在一個指向當前對象的實例局部變量
    局部變量表中也會預留出第一個Slot位來存放對象實例的引用,方法參數值從1開始計算,這個處理只會對實例方法有效,對靜態方法無效

  • Exceptions屬性(下列方法將會拋出受查異常,方法描述時在throws關鍵字後面列舉的異常)
    u2 attribute_name_index 1
    u4 attribute_length 1
    u2 number_of_exceptions 1
    u2 exception_index_table number_of_exceptions

  • LineNumberTable屬性
    描述Java源碼行號與字節碼行號(字節碼的偏移量)之間的對應關係不是運行時必須屬性,但會默認生成到Class文件中
    可以在Javac中使用-g:none/lines來取消生成這項信息不生成該屬性,拋出異常時,堆棧中部會顯示出錯的行號,並且在調試的時候,也無法按照源碼來設置斷點

  • LocalVariableTable屬性
    描述棧幀中局部變量表中變量與Java源碼中定義的變量之間的關係; 不是運行時必須屬性,但會默認生成到Class文件中; 可以在Javac中使用-g:none/vars來取消或者要求生成該屬性或不生成該屬性; 當其他人引入該方法時,所有的參數名稱都會丟失,IDE將會使用arg0,arg1之類的佔位符代替原有的參數名,但對程序運行沒有影響

  • SourceFile屬性:
    記錄生成Class文件的源碼文件名稱
    可選的屬性
    可以在Javac中使用-g:none/source來關閉/生成這項信息
    不開啓,內部類拋出異常的時候,堆棧不會顯示出錯代碼所屬文件名

  • ConstantValue屬性:
    通知虛擬機自動爲靜態變量賦值
    如果是非靜態變量,賦值在方法中
    靜態:被final static修飾,並且是基本類型/String,就要生成ConstantValue來初始化; 否則一致在方法中初始化

  • InnerClass屬性:
    記錄內部類與宿主類之間的關聯;如果定義內部類,編譯器會爲他及內部類生成InnerClass屬性
    Synthetic屬性:表示字段或方法不是由Java源代碼直接生成的,而是由編譯器添加
    其他屬性不做介紹.很少使用

JVM字節碼指令

一個字節長度,代表某種特定操作含義的數字(操作碼Opcode)以及後面的0至多個代表此操作所需參數(操作數Operands)構成

  • JVM面向操作數棧,而非寄存器:
    優勢:放棄操作數長度對齊,省略很多空格換行符
    缺點:導致解釋執行字節碼損失性能
  • 字節碼與數據類型:
    i-int,l-long,s-short,b-byte,c-char,f-float,d-double,a-reference
    加載儲存指令(用於將數據在棧幀中的局部變量表和操作數棧之間來回傳輸):
    將一個局部變量加載到操作棧: iload,iload_,…其他以此類推
    將一個數值從操作數棧存儲到局部變量表: istore,istore_,…其他以此類推
    將一個常量加載到操作數
  • 棧: bipush,sipush,ldc,ldc_w,ldc2_w,aconst_null,iconst_ml,lconst_,fconst_,dconst_
    擴充局部變量表的訪問索引:wide, 注:等,標示一組指令,如:iload_代表iload_0,iload_1…
  • 運算指令:
    用於對兩個操作數棧上的值進行特定運算,並把結構重寫存入操作數棧,指令如下:
    加法:iadd,ladd,fadd,dadd
    減法:isub,lsub,fsub,dsub
    乘法:imul,lmul,fmul,dmul
    除法:idiv,ldiv,fdiv,ddiv
    求餘:irem,lrem,frem,drem
    取反:ineg,lneg,fneg,dneg
    位移:ishl,ishr,iushr,lshl,lshr,lushr
    按位或:ior,lor
    按位與:iand,land
    按位異或:ixor,lxor
    局部變量:iinc
    比較指令:dcmpg,dcmpl,fcmpg,fcmpl,lcmp
    注:byte,short,char,boolean,沒有直接支持的算術指令,操作時使用int類型指令替換; 針對運算數出現操作溢出,JVM並不會拋出異常,只會返回結果NaN(null的意思)
  • 類型轉換指令:
    將兩種不同的數據類型進行互相轉換,操作一般用於實現用戶代碼中的顯式轉換
    直接支持寬化類型轉換,向上,小轉大
    int->long,float,double等等
    處理窄化類型轉換,必須顯式使用轉化指令
    i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f
  • 對象創建於訪問指令:
    創建對象:new
    創建數組指令:newarray,anewarray,multianewarray
    訪問類字段和實例字段指令:getfield,putfield,getstatic,putstatic
    把一個數組元素加載到操作數棧:baload,caload,saload,iaload,laload,faload,daload,aaload
    將一個操作數棧的值存儲到數組元素當中:bastore,castore,sastore,iastore,fastore,dastore,aastore
    取數組長度:arraylength
    檢查類實例類型:instanceof,checkcast
    操作數棧管理指令:
    將操作數棧的棧頂一個或兩個元素出棧:pop,pop2
    複製棧頂一個或兩個數值並將複製值或者雙份的複製值重新壓入棧頂:dup,dup2,dup_x1,dup2_x1,dup_x2,dup_x2,dup2_x2
    將棧最頂端的兩個數值交換:swap
  • 控制轉移指令:
    條件分支:ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonull,if_icmpeq,if_icmpne,if_icmplt,if_icmpgt,if_icmple,if_icmpge,if_acmpeq,if_acmpne
    複合條件分支:tableswitch,lookupswitch
    無條件分支:goto,goto_w,jsr,jsr_w,ret
  • 方法調用和返回:
    調用對象的實例方法:invokevirtual
    調用接口方法:invokeinsterface
    調用一些需要特殊處理的實例方法(實例初始化方法,私有方法,父類方法):invokespecial
    調用靜態方法:invokestatic
    在運行時動態解析出調用點限定符引用的方法,並執行該方法:invokedynamic; 異常處理:athrow(throw)
  • 同步指令:
    JVM支持方法及同步和方法內部一段指令序列的同步,這兩種同步結構都是使用Monitor來支持的
    方法級的同步是隱式的,無需通過字節碼指令控制,它是現在方法調用和返回操作之中,JVM從方法常量池的方法表結構中ACC_SYNCHRONIZED訪問標誌得知一個方法是否聲名爲同步方法,當方法調用,調用指令將會檢查方法的ACC_SYNCHRONIZED是否被設置,如果設置了,執行線程就要求持有Monitor,然後才能執行方法,當方法完成時釋放Monitor
    注:編譯器必須確保無論方法通過何種方式完成,方法中調用過得每條monitorenter指令都必須執行對應的monitorexit指令(無論方法正常/異常結束)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章