1 官網java
1.1 尋找JDK文檔過程程序員
www.oracle.com -> 右下角Product Documentation -> 往下拉選擇Java -> Java SE documentation面試
-> Previous releases -> JDK 8 -> 此時定位到: sql
1.2 The relation of JDK/JRE/JVM數據庫
Reference -> Developer Guides -> 定位到: 數組
Oracle has two products that implement Java Platform Standard Edition (Java SE) 8: Java SE
Development Kit (JDK) 8 and Java SE Runtime Environment (JRE) 8.
JDK 8 is a superset of JRE 8, and contains everything that is in JRE 8, plus tools such as the
compilers and debuggers necessary for developing applets and applications. JRE 8 provides the
libraries, the Java Virtual Machine (JVM), and other components to run applets and applications
written in the Java programming language. Note that the JRE includes components not required by
the Java SE specification, including both standard and non-standard Java components.
複製代碼
2 源碼到類文件tomcat
2.1 源碼bash
java
class Person{
private String name;
private int age;
private static String address;
private final static String hobby="Programming";
public void say(){
System.out.println("person say...");
}
public int calc(int op1,int op2){
return op1+op2;
}
}
複製代碼
編譯: javac Person.java ---> Person.class網絡
2.2 編譯過程數據結構
-> 註解抽象語法樹 -> 字節碼生成器 -> Person.class文件
2.3 類文件(Class文件)
官網TheclassFileFormat:
cafe babe 0000 0034 0027 0a00 0600 1809
0019 001a 0800 1b0a 001c 001d 0700 1e07
001f 0100 046e 616d 6501 0012 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 0100
0361 6765 0100 0149 0100 0761 6464 7265
複製代碼
......
magic(魔數):
The magic item supplies the magic number identifying the class file format; it has the value 0xCAFEBABE .
cafe babe
複製代碼
minor_version, major_version
0000 0034 對應10進制的52,表明JDK 8中的一個版本
constant_pool_count
0027 對應十進制27,表明常量池中27個常量
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];
}
複製代碼
.class字節碼文件
魔數與class文件版本
常量池
訪問標誌
類索引、父類索引、接口索引
字段表集合
方法表集合
屬性表集合
2.4 javap文件分解器
> javap -c Person.class > Person.txt
複製代碼
查看字節碼信息:結構信息/元數據/方法信息
Compiled from "Person.java"
class Person {
Person();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void say();
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String person say...
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public int calc(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: ireturn
}
複製代碼
3 類文件到虛擬機(類加載機制)
類加載機制
虛擬機把Class文件加載到內存
並對數據進行校驗,轉換解析和初始化
造成能夠虛擬機直接使用的Java類型,即java.lang.Class
3.1 裝載(Load)
查找和導入class文件
(1)經過一個類的全限定名獲取定義此類的二進制字節流
(2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構
(3)在Java堆中生成一個表明這個類的java.lang.Class對象,做爲對方法區中這些數據的訪問入口
3.2 連接(Link)
3.2.1 驗證(Verify)
保證被加載類的正確性
3.2.2 準備(Prepare)
爲類的靜態變量分配內存,並將其初始化爲默認值
3.2.3 解析(Resolve)
把類中的符號引用轉換爲直接引用
3.3 初始化(Initialize)
對類的靜態變量,靜態代碼塊執行初始化操做
3.4 類加載機制圖解
使用和卸載不算是類加載過程當中的階段,只是畫完整了一下
4 類裝載器ClassLoader
在裝載(Load)階段,其中第(1)步:經過類的全限定名獲取其定義的二進制字節流,須要藉助類裝載器完成,顧名思義,就是用來裝載Class文件的。
(1)經過一個類的全限定名獲取定義此類的二進制字節流
4.1 分類
4.2 圖解
4.3 加載原則
檢查某個類是否已經加載:順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個Classloader已加載,就視爲已加載此類,保證此類只全部ClassLoader加載一次。
加載的順序:加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。
雙親委派機制
定義:若是一個類加載器在接到加載類的請求時,它首先不會本身嘗試去加載這個類,而是把這個請求任務委託給父類加載器去完成,依次遞歸,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成此加載任務時,才本身去加載。
優點:Java類隨着加載它的類加載器一塊兒具有了一種帶有優先級的層次關係。好比,Java中的Object類,它存放在rt.jar之中,不管哪個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,所以Object在各類類加載環境中都是同一個類。若是不採用雙親委派模型,那麼由各個類加載器本身取加載的話,那麼系統中會存在多種不一樣的Object類。
5 運行時數據區(Run-Time Data Areas)
在裝載階段的第(2),(3)步能夠發現有運行時數據,堆,方法區等名詞
(2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構
(3)在Java堆中生成一個表明這個類的java.lang.Class對象,做爲對方法區中這些數據的訪問入口
說白了就是類文件被類裝載器裝載進來以後,類中的內容(好比變量,常量,方法,對象等這些數據得要有個去處,也就是要存儲起來,存儲的位置確定是在JVM中有對應的空間)
5.1 官網歸納
Summary
The Java Virtual Machine defines various run-time data areas that are used during execution of a program. Some of these data areas are created on Java Virtual Machine start-up and are destroyed only when the Java Virtual Machine exits. Other data areas are per thread. Per-thread data areas are created when a thread is created and destroyed when the thread exits.
5.2 圖解
5.3 常規理解
5.3.1 Method Area(方法區)
方法區是各個線程共享的內存區域,在虛擬機啓動時建立。
用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻又一個別名叫作Non-Heap(非堆),目的是與Java堆區分開來。
當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads.
The method area is created on virtual machine start-up.
Although the method area is logically part of the heap,......
If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.
此時回看裝載階段的第2步:
(2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構
若是這時候把從Class文件到裝載的第(1)和(2)步合併起來理解的話,能夠畫個圖
值得說明的
(1)方法區在JDK 8中就是Metaspace,在JDK6或7中就是Perm Space
(2)Run-Time Constant Pool
Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池,用於存放編譯時期生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池中存放。
Each run-time constant pool is allocated from the Java Virtual Machine's method area (§2.5.4).s
5.3.2 Heap(堆)
Java堆是Java虛擬機所管理內存中最大的一塊,在虛擬機啓動時建立,被全部線程共享。
Java對象實例以及數組都在堆上分配。
The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
The heap is created on virtual machine start-up.
此時回看裝載階段的第3步:
(3)在Java堆中生成一個表明這個類的java.lang.Class對象,做爲對方法區中這些數據的訪問入口
此時裝載(1)(2)(3)的圖能夠改動一下
5.3.3 Java Virtual Machine Stacks(虛擬機棧)
通過上面的分析,類加載機制的裝載過程已經完成,後續的連接,初始化也會相應的生效。
假如目前的階段是初始化完成了,後續作啥呢?確定是Use使用咯,不用的話這樣折騰來折騰去有什麼意義?那怎樣才能被使用到?換句話說裏面內容怎樣才能被執行?好比經過主函數main調用其餘方法,這種方式其實是main線程執行以後調用的方法,即要想使用裏面的各類內容,得要以線程爲單位,執行相應的方法才行。
那一個線程執行的狀態如何維護?一個線程能夠執行多少個方法?這樣的關係怎麼維護呢?
虛擬機棧是一個線程執行的區域,保存着一個線程中方法的調用狀態。換句話說,一個Java線程的運行狀態,由一個虛擬機棧來保存,因此虛擬機棧確定是線程私有的,獨有的,隨着線程的建立而建立。
每個被線程執行的方法,爲該棧中的棧幀,即每一個方法對應一個棧幀。
調用一個方法,就會向棧中壓入一個棧幀;一個方法調用完成,就會把該棧幀從棧中彈出。
Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6).
畫圖理解棧和棧幀
棧幀:每一個棧幀對應一個被調用的方法,能夠理解爲一個方法的運行空間。
A frame is used to store data and partial results, as well as to perform dynamic linking, return values for methods, and dispatch exceptions.
A new frame is created each time a method is invoked. A frame is destroyed when its method invocation completes, whether that completion is normal or abrupt (it throws an uncaught exception).
......
Note that a frame created by a thread is local to that thread and cannot be referenced by any other thread.
每一個棧幀中包括局部變量表(Local Variables)、操做數棧(Operand Stack)、指向運行時常量池的引用(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。
局部變量表:方法中定義的局部變量以及方法的參數存放在這張表中局部變量表中的變量不可直接使用,如須要使用的話,必須經過相關指令將其加載至操做數棧中做爲操做數使用。
操做數棧:以壓棧和出棧的方式存儲操做數的
動態連接:每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接(Dynamic Linking)。
方法返回地址:當一個方法開始執行後,只有兩種方式能夠退出,一種是遇到方法返回的字節碼指令;一種是碰見異常,而且這個異常沒有在方法體內獲得處理。
5.3.4 The pc Register(程序計數器)
咱們都知道一個JVM進程中有多個線程在執行,而線程中的內容是否可以擁有執行權,是根據CPU調度來的。
假如線程A正在執行到某個地方,忽然失去了CPU的執行權,切換到線程B了,而後當線程A再得到CPU執行權的時候,怎麼能繼續執行呢?這就是須要在線程中維護一個變量,記錄線程執行到的位置。
程序計數器佔用的內存空間很小,因爲Java虛擬機的多線程是經過線程輪流切換,並分配處理器執行時間的方式來實現的,在任意時刻,一個處理器只會執行一條線程中的指令。所以,爲了線程切換後可以恢復到正確的執行位置,每條線程須要有一個獨立的程序計數器(線程私有)。
若是線程正在執行Java方法,則計數器記錄的是正在執行的虛擬機字節碼指令的地址;
若是正在執行的是Native方法,則這個計數器爲空。
The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java
Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual
Machine thread is executing the code of a single method, namely the current method (§2.6) for
that thread. If that method is not native, the pc register contains the address of the Java
Virtual Machine instruction currently being executed. If the method currently being executed by
the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the
specific platform.
複製代碼
5.3.5 Native Method Stacks(本地方法棧)
若是當前線程執行的方法是Native類型的,這些方法就會在本地方法棧中執行。
5.4 結合字節碼指令理解虛擬機棧
java
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 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 o=obj;
return result;
}
public static void main(String[] args){
System.out.println(calc(1,2));
}
}
複製代碼
此時你須要一個可以看懂反編譯指令的寶典
好比我給你們準備了一個:
java
Compiled from "Person.java"
class Person {
Person();
Code:
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: aload_0
18: new #7 // class java/lang/Object
21: dup
22: invokespecial #1 // Method java/lang/Object."<init>":()V
25: putfield #8 // Field obj:Ljava/lang/Object;
28: return
public void say();
Code:
0: aload_0
1: getfield #8 // Field obj:Ljava/lang/Object;
4: astore_1
5: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #10 // String person say...
10: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: return
public static int calc(int, int);
Code:
0: iconst_3 //將int類型常量3壓入操做數棧
1: istore_0 //將int類型值存入局部變量0
2: iload_0 //從局部變量0中裝載int類型值
3: iload_1 //從局部變量1中裝載int類型值
4: iadd //執行int類型的加法
5: istore_2 //將int類型值存入局部變量2
6: iload_2 //從局部變量2中裝載int類型值
7: ireturn //從方法中返回int類型的數據
public static void main(java.lang.String[]);
Code:
0: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iconst_1
4: iconst_2
5: invokestatic #12 // Method calc:(II)I
8: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
11: return
}
複製代碼
5.5 結合類加載機制理解運行時數據區
5.5.1 裝載
值得探討的兩個方向:(1)類的裝載方式有哪些?(2)類裝載到底作了什麼?
類的裝載方式有哪些?
(1)本地系統加載
(2)網絡下載.class文件
(3)從zip,jar等歸檔文件中加載.class文件
(4)從數據庫中提取.class文件
(5)由java源文件動態編譯成.class文件
(6)Class.forName()加載
(7)ClassLoader.loadClass()加載
類裝載到底作了什麼?
(1)經過一個類的全限定名獲取定義此類的二進制字節流
這個階段是可控性比較強的階段,既能夠用系統提供的類加載器進行加載,又能夠自定義類加載器進行加載。
(2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構
方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
說明:類信息
類的版本、字段、方法、構造方法、接口定義等
(3)類加載的最終產品是位於堆區中的Class對象。
Class對象封裝了類在方法區內的數據結構,而且向Java程序員提供了訪問方法區內的數據結構的接口。
在Java堆中生成一個表明這個類的java.lang.Class對象,做爲對方法區中這些數據的訪問入口
Java對象實例以及數組都在堆上分配Class類
java
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
複製代碼
5.5.2 連接
5.5.2.1 驗證
保證被加載類的正確性
文件格式驗證
驗證字節流是否符合Class文件格式規範,好比是否以0xCAFEBABE開頭,主次版本號是否在當前虛擬機的處理範圍以內,常量池中的常量是否有不被支持的類型。
元數據驗證
對字節碼描述的信息進行語義分析,保證其符合Java語言規範的要求。
字節碼驗證
經過數據流和控制流分析,肯定程序語義是合法的、符合邏輯的。
符號引用驗證
確保解析動做能正確執行。
小結:驗證階段很重要,但不是必須的。若所引用的類通過反覆驗證沒問題,可使用-Xverifynone參數關閉大部分類驗證措施,從而縮短虛擬機類加載的時間。
5.5.2.2 準備
爲類的靜態變量分配內存,並將其初始化爲默認值
在方法區中,爲類變量分配內容並設置初始值
(1)內存分配僅僅是類變量,也就是static類型的變量。不包含實例變量,實例變量會在對象實例化時隨對象分配在堆中。
(2)這裏的默認值是根據類型賦值,不是在代碼中顯示賦予的值。
5.5.2.3 解析
把類中的符號引用轉換爲直接引用
Run-Time Constant Pool
Class文件中除了有類的版本、字段、方法、接口等描述 信息外,還有一項信息就是常量池,用於存放編譯時期生
成的各類字面量和符號引用,這部份內容將在類加載後進 入方法區的運行時常量池中存放。
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。
解析動做主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用限定符7類符號引用進行。
符號引用就是一組符號來描述目標,能夠是任何字面量。
直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
5.5.3 初始化
執行類構造器<clinit>,爲類的靜態變量賦予正確的初始值,有兩種方式
(1)直接給類變量指定初始值
(2)經過靜態代碼塊爲類變量指定初始值
類的初始化步驟
(1)若是這個類尚未被加載和連接,那先進行加載和連接
(2)假如這個類存在直接父類,而且這個類尚未被初始化(在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口)
(3)假如類中存在初始化語句(如static變量和static塊),那就依次執行這些初始化語句。
類何時纔會被初始化?
(1)建立類的實例
(2)訪問某個類或接口的靜態變量,或者對該靜態變量進行賦值
(3)調用類的靜態方法
(4)反射[Class.forName("com.XXX")]
(5)初始化一個類的子類(由於會先初始化父類)
(6)JVM啓動時代表的啓動類