1. 計算機原理
2. JVM 原理
2.1 Class文件
2.2 類文件到虛擬機
2.2.3 初始化(Initialize)
2.2.4 類加載器 classLoader
小結
今天主要分享 JVM 的基礎知識。
1. 計算機原理1.1 計算機體系結構遵循馮諾依曼計算機結構
計算機處理數據過程
提取階段: 由輸入設備把原始數據或信息輸入給計算機存儲器存起來
解碼階段: 根據CPU的指令集架構(ISA)定義將數值解譯為指令
執行階段: 再由控制器把需要處理或計算的數據調入運算器
本質上就是CPU取數據指令然後返回
CPU=存儲器+運算器+控制器
1.2 JVM 作用.機器語言
我們把CPU能夠直接認識的數據指令,稱為機器語言,也就是010101001這種形式
不同廠商的 CPU
不同 CPU 使用的 CPU 指令集是不一樣的,這就會有不兼容的問題;而且要是直接操作 01 這種形式的,非常麻煩並且容易出錯,硬體資源管理起來也不方便。
作業系統
2. JVM 原理官方文檔
JDK Reference
JDK8 JVM Reference
package com.example.jvm.clazz;
class Person {
private String name = "Jack";
private int age;
private final double salary = 100;
private static String address;
private final static String hobby = "Programming";
private static Object obj = new Object();
public void say() {
System.out.println("person say...");
}
public static int calc(int op1, int op2) {
op1 = 3;
int result = op1 + op2;
Object obj = new Object();
return result;
}
public static void main(String[] args) {
calc(1, 2);
}
}編譯生成 class 文件:javac -g:vars Person.java
vim Person.class
前期編譯
源文件Person.java
2.1.2 查看字節碼文件vim Person.class ,然後輸入:%!xxd 就是以16進位顯示class文件了,內容如下:
Tips
linux下查看二進位文件
以十六進位格式輸出:
od [選項] 文件
od -d 文件 十進位輸出
-o 文件 八進位輸出
-x 文件 十六進位輸出
xxd 文件 輸出十六進位
在vi命令狀態下:
:%!xxd :%!od 將當前文本轉化為16進位格式
:%!xxd -c 12 每行顯示12個字節
:%!xxd -r 將當前文本轉化回文本格式
2.1.3 類文件結構官網說明:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
ClassFile {
u4 magic;
u2 minor_version;
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];
}分析
u4 magic;
The magic item supplies the magic number identifying the class file format; it has the value 0xCAFEBABE.
提供了一個魔法值來聲明類文件格式,對應值為cafe babe
u2 minor_version;
u2 major_version;
0000 0034 16進位的,對應10進位:16*3+4=52,標識 JDK8
u2 constant_pool_count;The value of the constant_pool_count item is equal to the number of entries in the constant_pool table plus one. A constant_pool index is considered valid if it is greater than zero and less than constant_pool_count, with the exception for constants of type long and double noted in §4.4.5.
003f 表示有 3*16+15=62 個元素在常量池中(下標區間[1,constant_pool_count-1])。
cp_info constant_pool[constant_pool_count-1];
The constant_pool is a table of structures (§4.4) representing various string constants, class and interface names, field names, and other constants that are referred to within the ClassFile structure and its substructures. The format of each constant_pool table entry is indicated by its first "tag" byte.
The constant_pool table is indexed from 1 to constant_pool_count - 1.
常量池主要存儲兩方面內容:字面量(Literal)和符號引用(Symbolic References)
字面量:文本字符串,final修飾等符號引用:類和接口的全限定名、欄位名稱和描述符、方法名稱和描述符
2.1.3.1 javap 驗證javap -v -p Person.class > vp.txt 反編譯驗證字節碼和指令信息
// 打開 vp.txt
Classfile /Users/xiazhaoyang/Ashe/workspace/architectrue-adventure/code-examples/jdk-jvm-analysis/src/main/java/com/example/jvm/clazz/Person.class
Last modified 2020-3-31; size 982 bytes
MD5 checksum de6394397e12fac0f518b4de12f6cef9 // 魔法值
class com.example.jvm.clazz.Person
minor version: 0
major version: 52 // JDK 版本號
flags: ACC_SUPER
Constant pool: // 常量池
#1 = Methodref #10.#43 // java/lang/Object."<init>":()V
#2 = String #44 // Jack
#3 = Fieldref #13.#45 // com/example/jvm/clazz/Person.name:Ljava/lang/String;
#4 = Double 100.0d
#6 = Fieldref #13.#46 // com/example/jvm/clazz/Person.salary:D
#7 = Fieldref #47.#48 // java/lang/System.out:Ljava/io/PrintStream;
#8 = String #49 // person say...
#9 = Methodref #50.#51 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Class #52 // java/lang/Object
#11 = Methodref #13.#53 // com/example/jvm/clazz/Person.calc:(II)I
#12 = Fieldref #13.#54 // com/example/jvm/clazz/Person.obj:Ljava/lang/Object;
#13 = Class #55 // com/example/jvm/clazz/Person
#14 = Utf8 name
#15 = Utf8 Ljava/lang/String;
#16 = Utf8 age
#17 = Utf8 I
#18 = Utf8 salary
#19 = Utf8 D
#20 = Utf8 ConstantValue
#21 = Utf8 address
#22 = Utf8 hobby
#23 = String #56 // Programming
#24 = Utf8 obj
#25 = Utf8 Ljava/lang/Object;
#26 = Utf8 <init>
#27 = Utf8 ()V
#28 = Utf8 Code
#29 = Utf8 LocalVariableTable
#30 = Utf8 this
#31 = Utf8 Lcom/example/jvm/clazz/Person;
#32 = Utf8 say
#33 = Utf8 calc
#34 = Utf8 (II)I
#35 = Utf8 op1
#36 = Utf8 op2
#37 = Utf8 result
#38 = Utf8 main
#39 = Utf8 ([Ljava/lang/String;)V
#40 = Utf8 args
#41 = Utf8 [Ljava/lang/String;
#42 = Utf8 <clinit>
#43 = NameAndType #26:#27 // "<init>":()V
#44 = Utf8 Jack
#45 = NameAndType #14:#15 // name:Ljava/lang/String;
#46 = NameAndType #18:#19 // salary:D
#47 = Class #57 // java/lang/System
#48 = NameAndType #58:#59 // out:Ljava/io/PrintStream;
#49 = Utf8 person say...
#50 = Class #60 // java/io/PrintStream
#51 = NameAndType #61:#62 // println:(Ljava/lang/String;)V
#52 = Utf8 java/lang/Object
#53 = NameAndType #33:#34 // calc:(II)I
#54 = NameAndType #24:#25 // obj:Ljava/lang/Object;
#55 = Utf8 com/example/jvm/clazz/Person
#56 = Utf8 Programming
#57 = Utf8 java/lang/System
#58 = Utf8 out
#59 = Utf8 Ljava/io/PrintStream;
#60 = Utf8 java/io/PrintStream
#61 = Utf8 println
#62 = Utf8 (Ljava/lang/String;)V
// 欄位表集合
{
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
private int age;
descriptor: I
flags: ACC_PRIVATE
private final double salary;
descriptor: D
flags: ACC_PRIVATE, ACC_FINAL
ConstantValue: double 100.0d
private static java.lang.String address;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC
private static final java.lang.String hobby;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
ConstantValue: String Programming
private static java.lang.Object obj;
descriptor: Ljava/lang/Object;
flags: ACC_PRIVATE, ACC_STATIC
// 方法表集合
com.example.jvm.clazz.Person();
descriptor: ()V
flags:
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String Jack
7: putfield #3 // Field name:Ljava/lang/String;
10: aload_0
11: ldc2_w #4 // double 100.0d
14: putfield #6 // Field salary:D
17: return
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lcom/example/jvm/clazz/Person;
public void say();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String person say...
5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/example/jvm/clazz/Person;
public static int calc(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=2
0: iconst_3
1: istore_0
2: iload_0
3: iload_1
4: iadd
5: istore_2
6: new #10 // class java/lang/Object
9: dup
10: invokespecial #1 // Method java/lang/Object."<init>":()V
13: astore_3
14: iload_2
15: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 op1 I
0 16 1 op2 I
6 10 2 result I
14 2 3 obj Ljava/lang/Object;
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: iconst_1
1: iconst_2
2: invokestatic #11 // Method calc:(II)I
5: pop
6: return
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 args [Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: new #10 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: putstatic #12 // Field obj:Ljava/lang/Object;
10: return
}
2.1.3.2 常量池分析官網:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4
上面分析到常量池中常量的數量是62,接下來我們來具體分析一下這62個常量:
cp_info constant_pool[constant_pool_count-1] 也就是這塊包括的信息 ,
cp_info 其實就是一個表格的形式 All constant_pool table entries have the following general format:
cp_info {
u1 tag;
u1 info[];
}常量池標識符
Constant TypeValueCONSTANT_Class7CONSTANT_Fieldref9CONSTANT_Methodref10CONSTANT_InterfaceMethodref11CONSTANT_String8CONSTANT_Integer3CONSTANT_Float4CONSTANT_Long5CONSTANT_Double6CONSTANT_NameAndType12CONSTANT_Utf81CONSTANT_MethodHandle15CONSTANT_MethodType16CONSTANT_InvokeDynamic18常量池中的每一個元素必須以 1 byte 的標識符號作為開始,以此來表示它的具體 cp_info 類型。上表中列出的為官方聲明合法的標識符。
00000000: cafe babe 0000 0034 003f -> 0a00 0a00 2b08 ..4.?....+.
00000010: 002c 0900 0d00 2d06 4059 0000 0000 0000 .,....-.@Y.
接著往後面數一個 u1: 0a,表示10,對應 CONSTANT_Methodref ,表示這是一個方法引用。CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}往下數一個 u1: 00 標示起始位,對應其具體的 cp_info 類型
往下數兩個 u2
00 0a 對應 10, 代表的是class_index,表示該方法所屬的類在常量池中的索引
00 2b對應 32+11=43,代表的是name_and_type_index,表示該方法的名稱和類型的索引
#1 = Methodref #10.#43 // java/lang/Object."<init>":()V
往下數一個 u1 是 08,表示 CONSTANT_StringCONSTANT_String_info {
u1 tag;
u2 string_index;
}根據之前的規律,第一個 u1 表示類型,往後數一個 u2 表示對應存儲指向的索引位置
00000000: cafe babe 0000 0034 003f 0a00 0a00 2b08 ..4.?....+.
00000010: -> 002c 0900 0d00 2d06 4059 0000 0000 0000 .,....-.@Y.002c = 32 + 12 = 44;
#2 = String #44 // Jack後續的依此類推,簡而言之,Java 通過這種方式來表述一個類文件的結構。
2.1.3.3 結構劃分2.2 類文件到虛擬機所謂類加載機制就是
轉換解析和初始化形成可以虛擬機直接使用的Java 類型,即 java.lang.Class
2.2.1 裝載查找和導入class文件
將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構在Java堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區中這些數據的訪問入口Class 對象封裝了類在方法區內的數據結構,並且向 Java 程式設計師提供了訪問方法區內的數據結構的接口。在 Java 堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區中這些數據的訪問入口:
2.2.2 連結(Link)2.2.2.1 驗證(verify)保證被加載類的正確性
2.2.2.2 準備(prepare)為類的靜態變量分配內存,並將其初始化為默認值
graph TD
A[方法區]
A-->A1[靜態變量的初始化 int age=10]
A1 --> B[堆]
public class Demo1 {
private static int i;
public static void main(String[] args) {
// 正常列印出0,因為靜態變量i在準備階段會有默認值0
System.out.println(i);
}
}
public class Demo2 {
public static void main(String[] args) {
// 編譯通不過,因為局部變量沒有賦值不能被使用 int i;
System.out.println(i);
}
}
2.2.2.3 解析(resolve)將前文分析的類文件結構中的符號引用轉換為直接引用
符號引用就是一組符號來描述目標,可以是任何字面量。
直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。解析動作主要針對類或接口、欄位、類方法、接口方法、方法類型、方法句柄和調用限定符7類符號引用進行。
2.2.3 初始化(Initialize)對類的靜態變量,靜態代碼塊執行初始化操作
graph TD
A[方法區]
A-->A1[將類中的符號引用轉換為直接引用]
A1 --> B[堆]
2.2.4 類加載器 classLoader在裝載 (Load) 階段,其中第(1)步:通過類的全限定名獲取其定義的二進位字節流,需要藉助類裝載器完成,顧名思義,就是用來裝載Class 文件的。
2.2.4.1 分類Bootstrap ClassLoader 負責加載$JAVA_HOME中 jre/lib/rt.jar 裡所有的class或 Xbootclassoath選項指定的jar包。由C++實現,不是ClassLoader子類。Extension ClassLoader 負責加載java平臺中擴展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目錄下的jar包。App ClassLoader 負責加載classpath中指定的jar包及 Djava.class.path 所指定目錄下的類和 jar包。Custom ClassLoader 通過 java.lang.ClassLoader 的子類自定義加載 class,屬於應用程式根據 自身需要自定義的 ClassLoader,如 tomcat、jboss 都會根據 j2ee 規範自行實現 ClassLoader。2.2.4.2 圖解public class ClassLoaderTest {
public static void main(String[] args) {
// App ClassLoader
System.out.println(new ClassLoaderTest().getClass().getClassLoader());
// Ext ClassLoader
System.out.println(new ClassLoaderTest().getClass().getClassLoader().getParent());
// Bootstrap ClassLoader
System.out.println(new ClassLoaderTest().getClass().getClassLoader().getParent().getParent());
System.out.println(new String().getClass().getClassLoader());
}
}輸出
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@59690aa4
null
null
2.2.4.3 類加載原則(雙親委派)自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個Classloader已加 載,就視為已加載此類,保證此類只所有ClassLoader加載一次。
加載的順序 自頂向下,也就是由上層來逐層嘗試加載此類。protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果父加載器存在,委託給父類加載器加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
2.2.4.4 自定義加載器(破壞雙親委派)
小結JVM 文件結構是理解 JVM 運行時數據區和內存模型的必備知識,簡單總結下:
類文件要經過裝載、連結、初始化後才能在虛擬機中被使用Class 文件中定義了文件頭、常量池、欄位表和方法表