摘要:類文件的結構文件是一組以位字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在文件之中,中間沒有添加任何分隔符,這使得整個文件中存儲的內容幾乎全部是程序運行的必要數據,沒有空隙存在。
點擊進入我的博客 4.1 字節碼
平臺無關:Sun公司以及其他的虛擬機提供商發布了許多可以運行在各種不同平臺上的虛擬機,這些虛擬機都可以載入和執行同一種平臺無關的字節碼,從而實現了程序的“一次編寫,到處運行”。
語言無關:語言無關的基礎是虛擬機和字節碼存儲格式,Java虛擬機不和任何語言(包括Java)綁定,它只與Class文件這種特定的二進制文件格式所關聯,Class文件中包含了Java虛擬機指令集和符號表以及若干其他輔助信息。
Class文件是一組以8位字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內容幾乎全部是程序運行的必要數據,沒有空隙存在。當遇到需要占用8位字節以上空間的數據項時,則會按照高位在前的方式分割成若干個8位字節進行存儲。
Class文件只有兩種數據類型:無符號數、表。
無符號數:無符號數屬于基本的數據類型,以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節和8個字節的無符號數。無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。
表:表是由多個無符號數或其他表作為數據項構成的復合數據類型,表習慣性以_info結尾。表用于描述有層次的復合結構的數據,整個Class文件本質上就是一張表,由以下的數據項構成。
容量計數器:無論是無符號數還是表,當需要描述同一類型但數量不定的多個數據時,經常會使用一個前置的容量計數器加若干連續的數據項的形式。
4.2.1 魔數與Class文件的版本魔數:每個Class文件的頭4個字節稱為魔數(Magic Number),其唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件。值為0xCAFEBABE。
Class的版本號:緊接著魔數的4個字節存儲的是Class的版本號——第5個和第6個字節是次版本號(Minor Version),第7個和第8個字節是主版本號(Major Version)。
版本號兼容:高版本的JDK只能向下兼容以前版本的Class文件,不能運行以后版本的Class文件。
常量池:緊接著主次版本號后的是常量池,也可以理解為Class文件的資源倉庫,它是與其他項目關聯最多的數據類型,也是占用Class文件空間最大的數據項目之一,同時還算第一個出現的表類型數據項目。
常量池計數值:由于常量池中常量數量不固定,因此在入口處要放置一項u2類型的數據,代表常量池計數值(從1開始,因為計數的0代表“不引用任何一個常量池項目”的含義)。
常量池存放數據:常量池中主要存放兩大類常量——字面量(Literal)和符號引用(Symbolic References)。字面量比較接近于Java語言層面的常量概念——如文本字符串、聲明為final的常量值等。符號引用則屬于編譯原理方面的概念,包括下面三類常量:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符。
動態連接:Java代碼在javac編譯的時候,并沒有連接這一步驟,而是在虛擬機加載Class文件的時候動態連接。
常量池中的項:常量池中每一項都是一個表,截止到JDK 7中更用14種各不相同的表結構數據,其共同特點就是表開始的第一位是一個u1類型的標識位。
在常量池結束之后,緊接著的兩個字節代表訪問標志(access_flags),這個標志用于識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義為public類型;是否定義為abstract類型;如果是類的話,是否被聲明為final等。
類索引和父類索引:是一個u2類型的數據,用于確定這個類的全限定類名和父類的全限定類名,指向一個類型為CONSTANT_Class_info的類描述符常量,通過CONSTANT_Class_info類型的常量中的索引類型可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。
接口索引集合:是一組u2類型的數據集合,用于描述這個類實現了哪些接口,這些被實現的接口按照從左到右排列在接口索引集合中。入口的第一項——u2類型的數據為接口計數器,表示索引表的容量;如果沒有實現任何接口,則該計數器為0。
字段表:字段表(field_info)用于描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量,但不包括在方法內部聲明的局部變量。
一個字段包括的信息有:字段的作用域(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 |
標志名稱 | 標志值 | 含義 |
---|---|---|
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是對常量池的引用,代表著字段的簡單名稱。簡單名稱是指沒有類型和參數修飾的方法或者字段名稱,這個類中的inc()方法和m字段的簡單名稱分別是“inc”和“m”。
全限定名:以下面代碼為例,“org/xxx/clazz/TestClass”是這個類的全限定名,僅僅是把類全名中的“.”替換成了“/”而已,為了使連續的多個全限定名之間不產生混淆,在使用時最后一般會加入一個“;”表示全限定名結束。
public class TestClass { private int m; public int inc() { return m + 1; } }
descriptor_index也是對常量池的引用,代表著字段和方法的描述符。描述符的作用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。根據描述符規則,基本數據類型(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示。
標識字符 | 含義 | 標識字符 | 含義 |
---|---|---|---|
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;”,,一個整型數組“int[]”被記錄為“[I”。
描述方法:按照先參數列表,后返回值的順序描述,參數列表按照參數的嚴格順序放在一組小括號“( )”之內。如方法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_index為止就結束了,不過在descriptor_index之后跟隨著一個屬性表集合用于存儲一些額外的信息,字段都可以在屬性表中描述零至多項的額外信息。對于本例中的字段m,他的屬性表計數器為0,也就是說沒有需要額外描述的信息,但是,如果將字段m的聲明改為“final static int m=123”,那就可能會存在一項名稱為ConstantValue的屬性,其值指向常量123。
字段表集合中不會列出從超類或者父接口中繼承而來的字段,但有可能列出原本Java代碼之中不存在的字段,譬如在內部類中為了保持對外部類的訪問性,會自動添加指向外部類實例的字段。
在Java語言中字段是無法重載的,兩個字段的數據類型、修飾符不管是否相同,都必須使用不一樣的名稱,但是對于字節碼來講,如果兩個字段的描述符不一致,那字段重名就是合法的。
4.2.6 方法表集合方法表的結構如同字段表一樣,依次包括了訪問標志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表結合(attributes)幾項,如字段表所示。
標志名稱 | 標志值 | 含義 |
---|---|---|
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 | 方法是否由編譯器自動產生的 |
方法里的Java代碼,經過編譯器編譯成字節碼指令后,存放在方法屬性集合中一個名為“Code”的屬性里面,屬性表作為Class文件格式中最具擴展性的一種數據項目。
與字段表集合相對應的,如果父類方法在子類匯總沒有被重寫(Override),方法表集合中就不會出現來自父類的方法信息。
有可能會出現由編譯器自動添加的方法,最典型的便是類構造器“
在Java語言中,要重載(Overload)一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特征簽名,特征簽名就是一個方法中各個參數在常量池中的字段符號引用的集合,也就是因為返回值不會包含在特征簽名中,因此Java語言里面是無法僅僅依靠返回值的不同來對一個已有方法進行重載的。但是在Class文件格式匯總,特征簽名的范圍更大一些,只要描述符不是完全一致的兩個方法也可以共存。也就是說,如果兩個方法有相同的名稱和特征簽名,但返回值不同,那么也是可以合法共存于同一個Class文件中的。
4.2.7 屬性表集合在Class文件、字段表、方法表中都可以攜帶自己的屬性表集合,以用于描述某些場景專有的信息。與Class文件中其他的數據項目要求嚴格的順序、長度和內容不同,屬性表集合的限制稍微寬松了一些,不再要求各個屬性表具有嚴格順序,并且只要不與已有屬性名重復,任何人實現的編譯器都可以向屬性表寫入自己定義的屬性信息,Java虛擬機運行時會忽略掉他不認識的屬性。
屬性名稱需要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性的結構則是完全自定義的,只需要通過一個u4的長度屬性去說明屬性值所占用的位數即可。一個符合規則的屬性表應該滿足下表所定義的結構:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
屬性名稱 | 使用位置 | 含義 |
---|---|---|
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指令引用的引導方法限定符 |
Code屬性是Class文件中最重要的一個屬性,如果把一個Java程序中的信息分為代碼(Code,方法體里面的Java代碼)和元數據(Metadata,包括類、字段、方法定義及其他信息)兩部分,那么在整個Class文件中,Code屬性用于描述代碼,所有的其他數據項目都用于描述元數據。
Java程序方法體中的代碼經過Javac編譯器處理后,最終變為字節碼指令存儲在Code屬性內。Code屬性出現在方法表的屬性集合之中,但并非所有的方法表都必須存在這個屬性,譬如接口或者抽象類中的方法就不存在Code屬性。如果方法表有Code屬性存在,那么他的結構將如下表所示。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_locals | 1 |
u4 | code_length | 1 |
u1 | code | code_length |
u2 | exception_table_length | 1 |
exception_info | exception_table | exception_table_length |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
attribute_name_index:是一項指向CONSTANT_Utf8_info型常量的索引,常量值固定為“Code”,他代表了該屬性的屬性名稱。
attribute_length:指示了屬性值的長度,由于屬性名稱索引與屬性長度一共為6個字節,所以屬性值的長度固定為整個屬性表長度減少6個字節。
max_stack:代表了操作數棧(Operand Stacks)深度的最大值。在方法執行的任意時刻,操作數棧都不會超過這個深度。虛擬機運行的時候需要根據這個值分配棧幀(Stack Frame)中的操作幀深度。
max_locals:代表了局部變量表所需的存儲空間。在這里,max_locals的單位是Slot,Slot是虛擬機為局部變量分配內存所使用的最小單位。對于byte、char、float、int、short、boolean和returnAddress等長度不超過32位的數據類型,每個局部變量占用1個Slot,而double和long這兩種64位的數據類型則需要兩個Slot來存放。方法參數(包括實例方法中的隱藏參數“this”)、顯式異常處理器的參數(Exception Handler Parameter,就是try-catch語句中catch塊所定義的異常)、方法體中定義的局部變量都需要使用局部變量表來存放。另外,并不是在方法中用到了多少個局部變量,就把這些局部變量所占Slot之和作為max_locals的值,原因是局部變量表中的Slot可以重用,當代碼執行超出一個局部變量的作用域時,這個局部變量所占的Slot可以被其他局部變量所使用,Javac編譯器會根據變量的作用域來分配Slot給各個變量使用,然后計算出max_locals的大小。
code_length和code:用來存儲java源程序編譯后生成的字節碼指令。code_length代表字節碼長度,code是用于存儲字節碼指令的一系列字節流。既然叫字節碼指令,那么每個指令就是一個u1類型的單字節,當虛擬機讀取到code中的一個字節碼時,就可以對應找出這個字節碼代表的是什么指令,并且可以知道這條指令后面是否需要跟隨參數,以及參數應當如何理解。我們知道一個u1數據類型的取值范圍為0x00~0xFF,對應十進制的0~255,也就是一共可以表達256條指令,目前,Java虛擬機規范已經定義了其中約200條編碼值對應的指令含義。
關于code_length:有一件值得注意的事情,雖然他是一個u4類型的長度值,理論上最大值可以達到2的32次方減1,但是虛擬機規范中明確限制了一個方法不允許超過65535條字節碼指令,即他實際只使用了u2的長度,如果超過這個限制,Javac編譯器也會拒絕編譯。一般來講,編寫Java代碼時只要不是刻意去編寫一個超長的方法來為難編譯器,是不太可能超過這個最大值的限制。但是,某些特殊情況,例如在編譯一個很復雜的JSP文件時,某些JSP編譯器會把JSP內容和頁面輸出的信息歸并于一個方法之中,就可能因為方法生成字節碼超長的原因而導致編譯失敗。
這里的Exceptions屬性是在方法表與Code屬性平級的一項屬性。Exceptions屬性的作用是列舉出方法中可能拋出的受查異常(Checked Exceptions),也就是說方法描述時在throws關鍵字啊后面列舉的異常。他的結構見下表。
類型 | 名稱 | 數量 | 類型 | 名稱 | 數量 |
---|---|---|---|---|---|
u2 | attribute_name_index | 1 | u2 | number_of_exceptions | 1 |
u4 | attribute_length | 1 | u2 | exception_index_table | number_of_exceptions |
number_of_exceptions:項表示方法可能拋出number_of_exceptions種受查異常
exception_index_table:每一種受查異常使用一個exception_index_table項表示,exception_index_table是一個指向常量池中CONSTANT_Class_info型常量的索引,代表了該受查異常的類型。
LineNumberTable屬性用于描述Java源碼行號與字節碼行號(字節碼的偏移量)之間的對應關系。他并不是運行時必須的屬性,但默認生成到Class文件之中,可以在Javac中分別使用-g : none或-g : lines選項來取消或要求生成這項信息。如果選擇不生成LineNumberTable屬性,對程序運行產生的最主要的影響就是當拋出異常時,堆棧中將不會顯示出錯的行號,并且在調試程序的時候,也無法按照源碼行來設置斷點。LineNumberTable屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | line_number_table_length | 1 |
line_number_info | line_number_table | line_number_table_length |
line_number_table:是一個數量為line_number_table_length、類型為line_number_info的集合
line_number_info表:包括了start_pc和line_number兩個u2類型的數據項,前者是字節碼行號,后者是Java源碼行號。
LocalVariableTable屬性用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關系,她也不是運行時必須的屬性,但默認會生成到Class文件之中,可以在Javac中分別使用-g : none或-g :vars選項來取消或要求生成這項信息。如果沒有生成這項屬性,最大的影響就是當前其他人引用這個方法時,所有的參數名稱都將會丟失,IDE將會使用諸如arg0、arg1之類的占位符代替原有的參數名,這對程序運行沒有影響,但是會對代碼編寫帶來較大不便,而且在調試期間無法根據參數名稱從上下文中獲得參數值。LocalVariableTable屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | local_variable_table_length | 1 |
local_variable_info | local_variable_table | local_variable_table_length |
u2 | start_pc | 1 |
u2 | length | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | index | 1 |
start_pc和length:屬性分別代表了這個局部變量的生命周期開始地字節碼偏移量及其作用范圍覆蓋的長度,兩者結合起來就是這個局部變量在字節碼之中的作用域范圍。
name_index和descriptor_index:都是指向常量池中CONSTANT_Utf8_info型常量的索引,分別代表了局部變量的名稱以及這個局部變量的描述符。
index:是這個局部變量在棧幀局部變量表中Slot的位置。當這個變量數據類型是64位類型時(double和long),他占用的Slot為index和index+1兩個。
姐妹屬性:在JDK1.5引入泛型之后,LocalVariableTable屬性增加了一個“姐妹屬性”:LocalVariableTypeTable,這個新增的屬性結構與LocalVariableTable非常相似,僅僅是吧記錄的字段描述符的descriptor_index替換成了字段的特征簽名(Signature),對于非泛型類型來說,描述符和特征簽名能描述的信息是基本一致的,但是泛型引入后,由于描述符中反省的參數化類型被擦除掉,描述符就不能準確的描述泛型類型了,因此出現了LocalVariableTypeTable。
SourceFile屬性用于記錄生成這個Class文件的源碼文件名稱。這個屬性也是可選的,可以分別使用Javac的-g:none或-g: source選項來關閉或要求生成這項信息。在Java中,對于大多數的類來說,類名和文件名是一致的,但是有一些特殊情況(如內部類)例外。如果不生成這項屬性,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名。這個屬性是一個定長的屬性,其結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | sourcefile_index | ? |
sourcefile_index數據項:是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼我呢見的文件名。
ConstantValue屬性的作用是通知虛擬機自動為靜態變量賦值。只有被static關鍵字修飾的變量(類變量)才可以使用這項屬性。
類似“int x = 123”和“static int x=123”這樣的變量定義在Java程序中是非常常見的事情,但虛擬機對這兩種變量賦值的方法和時刻都有所不同。對于非static類型的變量(也就是實例變量)的賦值是在實例構造器
雖然有final關鍵字才更符合“ConstantValue”的語義,但虛擬機規范中并沒有強制要求字段必須設置了ACC_FINAL標志,只要求了有ConstantValue屬性的字段必須設置ACC_STATIC標志而已,對final關鍵字的要求是javac編譯器自己加入的限制。而對ConstantValue屬性值只能限于基本類型和String,不過不認為這是什么限制,因為此屬性的屬性值只是一個常量池的索引號,由于Class文件格式的常量類型中只有與基本屬性和字符串相對應的字面量,所以就算ConstantValue屬性在想支持別的類型也無能為力。ConstantValue屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | constantvalue_index | 1 |
ConstantValue屬性:是一個定長屬性,他的attribute_length數據項值必須固定為2。
constantvalue_index數據項:代表了常量池中一個字面量常量的引用,根據字段類型的不同,字面量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info、CONSTANT_String_info常量中的一種。
InnerClasses屬性用于記錄內部類與宿主類之間的關聯。如果一個類中定義了內部類,那編譯器將會為他以及他所包含的內部類生成InnerClasses屬性。該屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_class | 1 |
inner_classes_info | inner_class | number_of_classes |
number_of_classes:代表需要記錄多少個內部類信息。
inner_classes_info表:每一個內部類的信息都由一個inner_classes_info表進行描述。inner_classes_info的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | inner_class_info_index | 1 |
u2 | outer_class_info_index | 1 |
u2 | inner_name_index | 1 |
u2 | inner_class_access_info | 1 |
inner_name_index:是指向常量池中CONSTANT_Utf8_info型常量的索引,代表這個內部類的名稱,如果是匿名內部類,那么這項值為0.
inner_class_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_INTERFACE | 0x0020 | 內部類是否為synchronized |
ACC_ABSTRACT | 0x0400 | 內部類是否為abstract |
ACC_SYNTHETIC | 0x1000 | 內部類是否嬪妃由用戶代碼產生的 |
ACC_ANNOTATION | 0x2000 | 內部類是否是一個注解 |
ACC_ENUM | 0x4000 | 內部類是否是一個枚舉 |
Deprecated和Synthetic兩個屬性都屬于標志類型的布爾屬性,只存在有和沒有的區別,沒有屬性值的概念。屬性的結構非常簡單,其中attribute_length數據項的值必須為0x00000000,因為沒有任何屬性值需要設置,見下表:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
Deprecated屬性用于表示每個類、字段或者方法,已經被程序作者定位不在推薦使用,他可以通過在代碼中使用@deprecated注釋進行設置。
Synthetic屬性代表此字段或者方法并不是由Java源碼直接產生的,而是由編譯器自行添加的,在JDK 1.5之后,標識一個類、字段或者方法是編譯器自動產生的,也可以設置他們訪問標志中的ACC_SYNTHETIC標志位,其中最典型的例子就是Bridge Method。所有由非用戶代碼產生的類、方法及字段都應當至少設置Synthetic屬性和ACC_SYNTHETIC標志位中的一項,唯一的例外是實例構造器“
???????
StackMapTable屬性在JDK 1.6發布周增加到了Class文件規范中,他是一個復雜的變長屬性,位于Code屬性的屬性表,這個屬性會在虛擬機類加載的字節碼驗證階段被新類型檢查驗證器(Type Checker)使用,目的在于代替以前比較消耗性能的基于數據流分析的類型推導驗證器。
這個類型檢查驗證器最初來源于Sheng Liang為Java ME CLDC實現的字節碼驗證器。新的驗證器在同樣能保證Class文件合法性的前提下,省略了在運行期通過數據流分析確認字節碼的行為邏輯合法性的步驟,而是在編譯階段將一系列的驗證類型(Verification Types)直接記錄在Class文件之中,通過檢查這些驗證類型代替了類型推導過程,從而大幅提升了字節碼驗證的性能。這個驗證器在JDK 1.6中首次提供,并在JDK 1.7中強制代替原本基于類型推斷的字節碼驗證器。
StackMapTable屬性中包含零至多個棧映射棧(Stack Map Frames),每個棧映射幀都顯示或隱式的代表了一個字節碼偏移量,用于表示該執行到該字節碼時局部變量表和操作數棧的驗證類型。類型檢查驗證器會通過檢查目標方法的局部變量和操作數棧所需要的類型來確定一段字節碼指令是否符合邏輯約束。StackMapTable屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_entries | 1 |
stack_map_frame | stack_map_frame_entries | number_of_entries |
《Java虛擬機規范(Java SE 7版)》明確規定:在版本號大于或等于50.0的Class文件中,如果方法的Code屬性中沒有附帶StackMapTable屬性,那就意味著他帶有一個隱式的StackMap屬性。這個StackMap屬性的作用等同于number_of_entries值為0的StackMapTable屬性。一個方法的Code屬性最多只能有一個StackMapTable屬性,否則將拋出ClassFormatError異常。
Signature屬性在JDK 1.5發布后增加到了Class文件規范之中,他是一個可選的定長屬性,可以出現于類、屬性表和方法表結構的屬性表中。在JDK 1.5大幅增強了Java語言的語法,在此之后,任何類、接口、初始化方法或成員的泛型簽名如果包含餓了類型變量(Type Variables)或參數化類型(Parameterized Types),則Signature屬性會為他記錄泛型簽名信息。之所以要專門使用這樣一個屬性去記錄泛型類型,是因為Java語言的泛型采用的是擦除法實現的偽泛型,在字節碼(Code屬性)中,泛型信息編譯(類型變量、參數化類型)之后都統統被擦除掉。使用擦除法的好處是實現簡單(主要修改Javac編譯器,虛擬機內部只做了很少的改動)、非常容易實現Backport,運行期也能夠節省一些類型所占的內存空間。但壞處是運行期就無法像C#等有真泛型支持的語言那樣,將泛型類型與用戶定義的普通類型同等對待,例如運行期做反射時無法獲得到泛型信息。Signature屬性就是為了彌補這個缺陷而增設的,現在Java的反射API能夠獲取泛型類型,最終的數據來源也就是這個屬性。Signature屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | signature_index | 1 |
其中signature_index項的值必須是一個對常量池的有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示類簽名、方法類型簽名或字段類型簽名。如果當前的Signature屬性是類文件的屬性,則這個結構表示類簽名,如果當前的Signature屬性是方法表的屬性,則這個結構表示方法類型簽名,如果當前Signature屬性是字段表的屬性,則這個結構表示字段類型簽名。
BootstrapMethods屬性在JDK 1.7發布后增加到了Class文件規范之中,他是一個復雜的變長屬性,位于類文件的屬性表中。這個屬性用于保存invokedynamic指令引用的引導方法限定符。《Java虛擬機規范(Java SE 7版)》規定,如果某個類文件結構的常量池中曾經出現過CONSTANT_InvokeDynamic_info類型的常量,那么這個類文件的屬性表中必須存在一個明確地BootstrapMethods屬性,另外,即使CONSTANT_InvokeDynamic_info類型的常量在常量池中出現過多次,類文件的屬性表中最多也只能一個BootstrapMethods屬性。BootstrapMethods屬性與JSR-292中的InvokeDynamic指令和java.lang.Invoke包關系非常密切。
目前的Javac暫時無法生成InvokeDynamic指令和BootstrapMethods屬性,必須通過一些非常規的手段才能使用到他們,也許在不久的將來,等JSR-292更加成熟一些,這種狀況就會改變。BootstrapMethods屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | num_bootstrap_methods | 1 |
bootstrap_method | bootstrap_methods | num_bootstrap_methods |
num_bootstrap_methods:項的值給出了bootstrap_methods[]數組中的引導方法限定符的數量。
bootstrap_methods[]數組:的每個成員包含了一個指向常量池CONSTANT_MethodHandle結構的索引值,他代表了一個引導方法,還包含了這個引導方法靜態參數的序列(可能為空)。
bootstrap_method:結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | bootstrap_method_ref | 1 |
u2 | num_bootstrap_arguments | 1 |
u2 | bootstrap_arguments | num_bootstrap_arguments |
bootstrap_method_ref:bootstrap_method_ref項的值必須是一個對常量池的有效索引。常量池在該索引處的值必須是一個CONSTANT_MethodHandle_info結構。
num_bootstrap_arguments:num_bootstrap_arguments項的值給出了bootstrap_arguments[]數組成員的數量。
bootstrap_arguments[]:bootstrap_arguments[]數組的每個成員必須是一個對常量池的有效索引。常量池在該索引處必須是下列結構之一:CONSTANT_String_info、CONSTANT_Class_info、CONSTANT_Integer_info、CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_MethodHandle_info或CONSTANT_MethodType_info。
4.3 字節碼指令Java虛擬機的指令由一個字節長度的、代表著某種特定操作含義的數字(稱為操作碼,Opcode)以及跟隨其后的零至多個代表此操作所需參數(稱為操作數,Operands)而構成。由于Java虛擬機采用面向操作數棧而不是寄存器的架構,所以大多數的指令都不包含操作數,只有一個操作碼。
操作碼總數:Java虛擬機操作碼的長度為一個字節,這意味著指令集的操作碼總數不可能超過256條
放棄操作數對齊:由于Class文件格式放棄了編譯后代碼的操作數長度對齊,這就意味著虛擬機處理那些超過一個字節數據的時候,不得不在運行時從字節中重建出具體數據的結構,如果要將一個16位長度的無符號整數使用兩個無符號字節存儲起來(將它們命名為byte1和byte2),那他們的值應該是這樣的:
(byte1 << 8) | byte24.3.1 字節碼與數據類型
大多數的指令都包含了其操作所對應的數據類型信息,iload指令用于從局部變量表中加載int型的數據到操作數棧中,而fload指令加載的則是float類型的數據。
大部分與數據類型相關的字節碼指令,他們的操作碼助記符中都有特殊的字符來表明專門為哪種數據類型服務:i代表對int類型的數據操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。
有一些指令的助記符中沒有明確地指明操作類型的字母,如arraylength指令,他沒有代表數據類型的特殊字符,但操作數永遠只能是一個數組類型的對象。
還有一些指令如無條件跳轉指令goto則是與數據類型無關的。
由于Java虛擬機的操作碼最多只有256個,Java虛擬機的指令被設計成非完全獨立的(Java虛擬機規范中把這種特性稱為“Not Orthogonal”,即并非每種數據類型和每一種操作都有對應的指令)。
大部分的指令都沒有支持整數類型byte、char和short,甚至沒有任何指令支持boolean類型。編譯器會在編譯器或運行期將byte和short類型的數據帶符號擴展(Sign-Extend)為相應的int類型數據,將boolean和char類型數據零位擴展(Zero-Extend)為相應的int類型數據。與之類似,在處理boolean、byte、short和char類型的數組時,也會轉換為使用對應的int類型的字節碼指令來處理。因此,大多數對于boolean、byte、short和char類型數據的操作,實際上都是使用相應的int類型作為運算類型(Computational Type)。
4.3.2 加載和存儲指令加載和存儲指令用于將數據在棧幀中的局部變量表和操作數棧之間來回傳輸,這類指令包括如下內容。
將一個局部變量加載到操作棧:
iload、iload_、lload、lload_ 、fload、fload_ 、dload、dload_ 、aload、aload_
將一個數值從操作數棧存儲到局部變量表:
istore、istore_、lstore、lstore_ 、fstore、fstore_ 、dstore、dstore_ 、astore、astore_
將一個常量加載到操作數棧:
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_、lconst_、fconst_ 、dconst_
擴充局部變量表的訪問索引的指令:
wide
以尖括號結尾的(例如iload_
運算或算術指令用于對兩個操作數棧上的值進行某種特定運算,并把結果重新存入到操作棧頂。大體上算術指令可以分為兩種:對整型數據進行運算的指令與對浮點型數據進行運算的指令。由于沒有直接支持byte、short、char和boolean類型的算術指令,對于這類數據的運算,應使用操作int類型的指令代替。整數與浮點數的算術指令在溢出和被零除的時候也有各自不同的行為表現,所有的算術指令如下:
加法指令: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。
在處理整型數據時,只有除法指令(idiv和ldiv)以及求余指令(irem和lrem)中當出現除數為零時會導致虛擬機拋出ArithmeticException異常,其余任何整型數運算場景都不應該拋出運行時異常。
對long類型數值進行比較時,虛擬機采用帶符號的比較方式,而
虛擬機在處理浮點數時必須嚴格遵循IEEE 754規范中所規定的行為和限制。也就是說,Java虛擬機必須完全支持IEEE 754中定義的非正規浮點數值(Denormalized Floating-Point Numbers)和逐級下溢(Gradual Underflow)的運算規則。
所有的運算結果都必須舍入到適當的精度,非精確的結果必須舍入為可被表示的最接近的精確值,如果有兩種可表示的形式與該值一樣接近,將優先選擇最低有效位為零的。
Java虛擬機在處理浮點數運算時,不會拋出任何運行時異常(這里所講的是Java語言中的異常,勿與IEEE 754規范中的浮點異常互相混淆,IEEE 754的浮點異常是一種運算信號),當一個操作產生溢出時,將會使用有符號的無窮大來表示,如果某個操作結果沒有明確的數學定義的話,將會使用NaN值來表示。所有使用NaN值作為操作數的算術操作,結果都會返回NaN。
對浮點數值進行比較時(dcmpg、dcmpl、fcmpg、fcmpl),虛擬機會采用IEEE 754規范所定義的無信號比較(Nonsignaling Comparisons)方式。
4.3.4 類型轉換指令類型轉換指令可以將兩種不同的數值類型進行相互轉換,JVM直接支持小范圍類型向大范圍類型的安全轉換,而處理大范圍類型到小范圍類型的窄化類型轉換則需要顯示地使用轉換指令來完成,這些指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。
窄化類型轉換會導致結果產生不同的正負號、不同的數量級、數值精度丟失的情況,但永遠不可能拋出運行時異常。
類實例與數組都屬于對象,但是其創建與操作使用了不同的字節碼指令,指令如下:
創建類實例:new
創建數組:newarray, anewarray, multianewarray
訪問類字段(static字段)和實例字段:getfield, putfield, getstatic, putstatic
把一個數組元素加載到操作數棧:baload, caload, saload, iaload, laload, faload, etc.
把一個操作數棧的值存儲到數組元素中:bastore, castore, sastore, iastore, etc.
取數組長度:arraylength
檢查類實例類型:instanceof, checkcast
4.3.6 操作數棧管理指令如同操作一個普通數據結構中的堆棧那樣,Java虛擬機提供了一些用于直接操作數棧的指令,包括:
將操作數棧的棧頂一個或兩個元素出棧:pop、pop2
復制棧頂一個或兩個數值并將復制值或雙份的復制值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
將棧最頂端的兩個數值互換:swap
4.3.7 控制轉移指令控制轉移指令可以讓Java虛擬機有條件或無條件的從指定的位置指令而不是控制轉移指令的下一條指令繼續執行程序,從概念模型上理解,可以認為控制轉移指令就是在有條件或無條件的修改PC寄存器的值。控制轉移指令如下。
條件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、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。
int、reference、null指令集:在Java虛擬機中有專門的指令集用來處理int和reference類型的條件分支比較操作;為了可以無需明顯標識一個實體值是否null,也有專門的指令用來檢測null值。
轉化成int類型:與算術運算時的規則一致,對于boolean類型、byte類型、char類型和short類型的條件分支比較操作,則會先執行相應類型的比較運算指令(dcmpg、dcmpl、fcmpg、fcmpl、lcmp),運算指令會返回一個整形值到操作數棧中,隨后再執行int類型的條件分支比較操作來完成整個分支跳轉。由于各種類型的比較最終都會轉化為int類型的比較操作,int類型比較是否方便完善就顯得尤為重要,所以Java虛擬機提供的int類型的條件分支指令是最為豐富和強大的。
方法調用指令與數據類型無關,而方法返回指令是根據返回值的類型區分的,包括ireturn(當返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn和areturn;另外還有一條return指令供聲明為void的方法、實例初始化方法以及類和接口的類初始化方法使用。以下列舉了5條用于方法調用的指令:
invokevirtual——指令用于調用對象的實例方法,根據對象的實際類型進行分派(虛方法分派),這也是Java語言中最常見的方法分派方式。
invokeinterface——指令用于調用接口方法,他會在運行時搜索一個實現了這個接口方法的對象,找出適合的方法進行調用。
invokespecial——指令用于調用一些需要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法。
invokestatic——指令用于調用類方法(static方法)。
invokedynamic——指令用于運算時動態解析出調用點限定符所引用的方法,并執行該方法,前面4條調用指令的分派邏輯都固化在Java虛擬機內部,而invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的。
4.3.9 異常處理指令在Java程序中顯示拋出異常的操作(throw 語句)都由athrow指令來實現
除了用throw語句顯式拋出異常情況之外,Java虛擬機規范還規定了許多運行時異常會在其他Java虛擬機指令檢測到異常狀況時自動拋出。
在Java虛擬機中,處理異常(catch語句)不是由字節碼指令來實現的(很久之前曾經使用jsr和ret指令來實現,現在已經不用了),而是采用異常表來完成的。
4.3.10 同步指令Java虛擬機可以支持方法級的同步和方法內部一段指令序列的同步,這兩種同步結構是使用管程(Monitor)來支持的。
方法級的同步是隱式的,即無需通過字節碼指令來控制,他實現在方法調用和返回操作之中。
虛擬機可以從方法常量池的方法表結構中的ACC_SYNCHRONIZED訪問標志得知一個方法是否聲明為同步方法。
當方法調用時,調用指令將會檢查方法的ACC_SYNCHRONIZED訪問標志是否被設置,如果設置了,執行線程就要求先成功持有管程,然后才能執行方法,最后當方法完成(無論是正常完成還是非正常完成)時釋放管程。
在方法執行期間,執行線程持有了管程,其他任何線程都無法再獲取到同一個管程。如果一個同步方法執行期間拋出了異常,并且在方法內部無法處理此異常,那么這個同步方法所持有的管程將在異常拋到同步方法之外時自動釋放。
同步一段指令集通常是由Java語言中的synchronized語句塊來表示的。
Java虛擬機的指令集中有monitorenter和monitorexit兩條指令來支持synchronized關鍵字的語義,正確實現synchronized關鍵字需要Javac編譯器與Java虛擬機兩者共同協作支持。
編譯器必須確保無論方法通過何種方式完成,方法中調用過的每條monitorenter指令都必須執行其對應的monitorexit指令,而無論這個方法是正常結束還是異常結束。
為了保證在方法異常完成時monitorenter和monoitorexit指令依然剋有正確配對執行,編譯器會自動產生一個異常處理器,這個異常處理器聲明可處理所有的異常,他的目的就是用來執行monitorexit指令。
4.4 公有設計和私有實現管程:管程可以看做一個軟件模塊,它是將共享的變量和對于這些共享變量的操作封裝起來,形成一個具有一定接口的功能模塊,進程可以調用管程來實現進程級別的并發控制。進程只能互斥得使用管程,即當一個進程使用管程時,另一個進程必須等待。當一個進程使用完管程后,它必須釋放管程并喚醒等待管程的某一個進程。在管程入口處的等待隊列稱為入口等待隊列,由于進程會執行喚醒操作,因此可能有多個等待使用管程的隊列,這樣的隊列稱為緊急隊列,它的優先級高于等待隊列。
信號量:信號量是一種抽象數據類型,由一個整形 (sem)變量和兩個原子操作組成:
P():sem減1,如果sem<0等待,否則繼續;
V():sem加1,如果sem<=0,說明當前有等著的進程,喚醒掛在信號量上的等待進程,可以是一個或多個 。
Java虛擬機規范描繪了Java虛擬機應有的共同程序存儲格式:Class文件格式以及字節碼指令集。這些內容與硬件、操作系統及具體的Java虛擬機實現之間是完全獨立的。
Java虛擬機實現必須能夠讀取Class文件并精確實現包含在其中的Java虛擬機代碼的語義,一個優秀的虛擬機實現,在滿足虛擬機規范的約束下對具體實現做出修改和優化也是完全可行的,并且虛擬機規范中明確鼓勵實現者這樣做。只要優化后Class文件依然可以被正確讀取,并且包含在其中的語義能得到完整的保持,那實現者就可以選擇任何方式去實現這些語義。
虛擬機實現者可以使用這種伸縮性來讓Java虛擬機獲得更高的性能、更低的內存消耗或者更好的可移植性,選擇哪種特性取決于Java虛擬機實現的目標和關注點是什么。虛擬機實現的方式主要有以下兩種:
將輸入的Java虛擬機代碼在加載或執行時翻譯成另外一種虛擬機的指令集。
將輸入的Java虛擬機代碼在加載或執行時翻譯成宿主CPU的本地指令集(即JIT代碼生成技術)。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72762.html
摘要:哪吒社區技能樹打卡打卡貼函數式接口簡介領域優質創作者哪吒公眾號作者架構師奮斗者掃描主頁左側二維碼,加入群聊,一起學習一起進步歡迎點贊收藏留言前情提要無意間聽到領導們的談話,現在公司的現狀是碼農太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區Java技能樹打卡?【打卡貼 day2...
摘要:如問到是否使用某框架,實際是是問該框架的使用場景,有什么特點,和同類可框架對比一系列的問題。這兩個方向的區分點在于工作方向的側重點不同。 [TOC] 這是一份來自嗶哩嗶哩的Java面試Java面試 32個核心必考點完全解析(完) 課程預習 1.1 課程內容分為三個模塊 基礎模塊: 技術崗位與面試 計算機基礎 JVM原理 多線程 設計模式 數據結構與算法 應用模塊: 常用工具集 ...
摘要:加載器種類啟動類加載器在中用來加載自身需要的類,實現,用來加載。那么就能保證的類會被優先加載,限制了使用者對系統的影響。這種方式下就完成類加載器的雙親委派機制此處會將作為參數傳入進去實際上是調用了方法 Class 文件的裝載流程 (類加載過程) 加載 -> 連接 (驗證 -> 準備 -> 解析) -> 初始化 -> 使用 -> 卸載 加載 加載階段,jvm 會通過類名獲取到此類的字節碼...
摘要:虛擬機包括一套字節碼指令集一組寄存器一個棧一個垃圾回收堆和一個存儲方法域。而使用虛擬機是實現這一特點的關鍵。虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。此內存區域是唯一一個在虛擬機規范中沒有規定任何情況的區域。 1、 什么是JVM? JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設備的規范,它是一個虛構出來的計算機,...
摘要:我的是忙碌的一年,從年初備戰實習春招,年三十都在死磕源碼,三月份經歷了阿里五次面試,四月順利收到實習。因為我心理很清楚,我的目標是阿里。所以在收到阿里之后的那晚,我重新規劃了接下來的學習計劃,將我的短期目標更新成拿下阿里轉正。 我的2017是忙碌的一年,從年初備戰實習春招,年三十都在死磕JDK源碼,三月份經歷了阿里五次面試,四月順利收到實習offer。然后五月懷著忐忑的心情開始了螞蟻金...
閱讀 3096·2021-09-28 09:42
閱讀 3448·2021-09-22 15:21
閱讀 1122·2021-07-29 13:50
閱讀 3564·2019-08-30 15:56
閱讀 3367·2019-08-30 15:54
閱讀 1196·2019-08-30 13:12
閱讀 1172·2019-08-29 17:03
閱讀 1198·2019-08-29 10:59