JVM的基礎學習到紮實--class文件詳解
Java工作幾年之後,特別是從基礎到工作一年以上,還有的是培訓班出來的,自學的,寫了大量的代碼,但是,這些代碼是怎麼工作的,怎麼執行的,怎麼變成我們需要的結果?對於沒有接觸過JVM的同學來說是一個問題。也有的不知道JVM是幹什麼用的,怎麼來的。這是問題根本。
作爲Java開發者,我們都知道,自己在eclipse寫的代碼,執行後就看到結果,但是執行過程中是怎麼樣的,需要我們去搞懂。
1.JVM幹什麼用的?
JVM就是虛擬機,專門是用來解析寫Java的字節碼,最後變成我們自己想要的結果。如下圖所示:
2.jdk,jre,jvm三者之間的關係
JDK有兩種,分別爲OracleJDK和OpenJDK
查看JDK的版本:
進去終端裏面輸入命令查看:java -version;
java -version;
顯示結果:
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
192:~ mac1094$
以上就是OracleJDK。
如果是OpenJDK,顯示信息:
openjdk version "1.8.0_144"
OpenJDK Runtime Environment (build 1.8.0_144-b01)
OpenJDK 64-Bit Server VM (build 25.144-b01, mixed mode)
JVM的運行模式有兩種:Server模式和Client模式
兩種模式的區別在於:
Client模式啓動速度較快,Server模式啓動較慢;
但是啓動進入穩定期長期運行之後Server模式的程序運行速度比Client要快很多。
因爲Server模式啓動的JVM採用的是重量級的虛擬機,對程序採用了更多的優化;而Client模式啓 動的JVM採用的是輕量級的虛擬機。所以Server啓動慢,但穩定後速度比Client遠遠要快。
3.JVM的架構圖(很重要)
根據架構圖中可以看出,JVM架構由三大板塊構成,首先要記住三大板塊的邏輯結構關係,然後再記住三大板塊裏的功能,切記死記硬背,理解的基礎上記憶,每天畫一下架構圖,堅持一週就可以記得牢固。
JVM的執行流程:
4.class文件是怎麼加載的?(重在理解)
我們隨便寫一個Java源代碼,打開他的Class文件(使用Hex Editor等其他工具打開),如下圖所示:
class文件的結構很複雜,需要理解:如下圖:
魔數:class文件最初的4個字節都是以"0xCAFEBABE"來的,判斷的唯一標準。
版本號:分爲主版本號和副版本號,各佔2字節。
minor_version( JDK次版本號),
major_version( JDK主版本號)
可以查看JVM編譯的代碼,使用命令工具:javap -v class文件名
192:myapp mac1094$ javap -v Math.class
顯示結果:
Classfile /Users/mac1094/Desktop/myapp/Math.class
Last modified May 17, 2019; size 401 bytes
MD5 checksum 1b66de1534877806cd9a6747dbfc330a
Compiled from "Math.java"
public class Math
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V
#4 = Class #19 // Math
#5 = Class #20 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 main
#11 = Utf8 ([Ljava/lang/String;)V
#12 = Utf8 SourceFile
#13 = Utf8 Math.java
#14 = NameAndType #6:#7 // "<init>":()V
#15 = Class #21 // java/lang/System
#16 = NameAndType #22:#23 // out:Ljava/io/PrintStream;
#17 = Class #24 // java/io/PrintStream
#18 = NameAndType #25:#26 // println:(I)V
#19 = Utf8 Math
#20 = Utf8 java/lang/Object
#21 = Utf8 java/lang/System
#22 = Utf8 out
#23 = Utf8 Ljava/io/PrintStream;
#24 = Utf8 java/io/PrintStream
#25 = Utf8 println
#26 = Utf8 (I)V
{
public Math();
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
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
14: iload_3
15: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
18: return
LineNumberTable:
line 3: 0
line 4: 2
line 5: 4
line 6: 11
line 7: 18
}
SourceFile: "Math.java"
常量池計數器:
常量池是class文件中非常重要的結構,它描述着整個class文件的字面量信息。常量池是由一組 constant_pool結構體數組組成的,而數組的大小則由常量池計數器指定。常量池計數器 constant_pool_count 的值 =constant_pool表中的成員數+ 1。constant_pool表的索引值只有在大於 0 且小於constant_pool_count時纔會被認爲是有效的。
常量池數據區:
字面量和符號引用
訪問標誌:
訪問標誌,access_flags 是一種掩碼標誌,用於表示某個類或者接口的訪問權限及基礎屬性。
類索引:
類索引,this_class的值必須是對constant_pool表中項目的一個有效索引值。constant_pool表 在這個索引處的項必須爲CONSTANT_Class_info 類型常量,表示這個 Class 文件所定義的類或接 口。
父類索引:
父類索引,對於類來說,super_class 的值必須爲 0 或者是對constant_pool 表中項目的一個有 效索引值。
如果它的值不爲 0,那 constant_pool 表在這個索引處的項必須爲CONSTANT_Class_info 類型常 量,表示這個 Class 文件所定義的類的直接父類。當前類的直接父類,以及它所有間接父類的 access_flag 中都不能帶有ACC_FINAL 標記。對於接口來說,它的Class文件的super_class項的 值必須是對constant_pool表中項目的一個有效索引值。constant_pool表在這個索引處的項必須爲 代表 java.lang.Object 的 CONSTANT_Class_info 類型常量 。
如果 Class 文件的 super_class的值爲 0,那這個Class文件只可能是定義的是 java.lang.Object類,只有它是唯一沒有父類的類。
接口計數器:
接口計數器,interfaces_count的值表示當前類或接口的【直接父接口數量】。
接口信息數據區:
接口表,interfaces[]數組中的每個成員的值必須是一個對constant_pool表中項目的一個有效索引 值, 它的長度爲 interfaces_count。每個成員interfaces[i] 必須爲 CONSTANT_Class_info類型常量,其中 【0 ≤ i <interfaces_count】。在interfaces[]數組 中,成員所表示的接口順序和對應的源代碼中給定的接口順序(從左至右)一樣,即interfaces[0]對 應的是源代碼中最左邊的接口。
字段計數器:
字段計數器,fields_count的值表示當前 Class 文件 fields[]數組的成員個數。 fields[]數組 中每一項都是一個field_info結構的數據項,它用於表示該類或接口聲明的【類字段】或者【實例字 段】。
字段信息數據區:
字段表,fields[]數組中的每個成員都必須是一個fields_info結構的數據項,用於表示當前類或接 口中某個字段的完整描述。 fields[]數組描述當前類或接口聲明的所有字段,但不包括從父類或父接 口繼承的部分。
方法計數器:
方法計數器, methods_count的值表示當前Class 文件 methods[]數組的成員個數。Methods[] 數組中每一項都是一個 method_info 結構的數據項。
方法信息數據區:
方法表,methods[] 數組中的每個成員都必須是一個 method_info 結構的數據項,用於表示當前類 或接口中某個方法的完整描述。
如果某個method_info 結構的access_flags 項既沒有設置 ACC_NATIVE 標誌也沒有設置 ACC_ABSTRACT 標誌,那麼它所對應的方法體就應當可以被 Java 虛擬機直接從當前類加載,而不需 要引用其它類。
method_info結構可以表示類和接口中定義的所有方法,包括【實例方法】、【類方法】、【實例初始 化方法】和【類或接口初始化方法】。
methods[]數組只描述【當前類或接口中聲明的方法】,【不包括從父類或父接口繼承的方法】。
屬性計數器:
屬性計數器,attributes_count的值表示當前 Class 文件attributes表的成員個數。 attributes表中每一項都是一個attribute_info 結構的數據項。
屬性信息數據區:
屬性表,attributes 表的每個項的值必須是attribute_info結構。
在Java 7 規範裏,Class文件結構中的attributes表的項包括下列定義的屬性: InnerClasses
、 EnclosingMethod 、 Synthetic 、Signature、SourceFile,SourceDebugExtension 、Deprecated、RuntimeVisibleAnnotations 、RuntimeInvisibleAnnotations以及 BootstrapMethods屬性。
對於支持 Class 文件格式版本號爲 49.0 或更高的 Java 虛擬機實現,必須正確識別並讀取 attributes表中的Signature、RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations屬性。對於支持Class文件格式版本號爲 51.0 或更高的 Java 虛擬機實現,必須正確識別並讀取 attributes表中的BootstrapMethods屬性。Java 7 規範 要求 任一 Java 虛擬機實現可以自動忽略 Class 文件的 attributes表中的若干 (甚至全部) 它不可 識別的屬性項。任何本規範未定義的屬性不能影響Class文件的語義,只能提供附加的描述信息 。
5.以上就是JVM的基礎的class文件的來源,很重要,需要記住。爲後面的特徵打好基礎。後續會有類加載機制和運行數據區,垃圾回收機制,如有錯誤,可以留言更正!謝謝!