1 官網(wǎng)
1.1 JDK8
1.2 The relation of JDK/JRE/JVM
Reference -> Developer Guides -> 定位到:https://docs.oracle.com/JAVAse/8/docs/index.html
“
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 源碼到類(lèi)文件
2.1 源碼
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;
}
}
2.2 編譯過(guò)程
“
Person.java -> 詞法分析器 -> tokens流 -> 語(yǔ)法分析器 -> 語(yǔ)法樹(shù)/抽象語(yǔ)法樹(shù) -> 語(yǔ)義分析器 -> 注解抽象語(yǔ)法樹(shù) -> 字節(jié)碼生成器 -> Person.class文件
2.3 類(lèi)文件(Class文件)
官網(wǎng)The class File Format :https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
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
minor_version, major_version
minor_version, major_version
constant_pool_count
0027 對(duì)應(yīng)十進(jìn)制27,代表常量池中27個(gè)常量
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字節(jié)碼文件
魔數(shù)與class文件版本
常量池
訪(fǎng)問(wèn)標(biāo)志
類(lèi)索引、父類(lèi)索引、接口索引
字段表集合
方法表集合
屬性表集合
3 類(lèi)文件到虛擬機(jī)(類(lèi)加載機(jī)制)
3.1 裝載(Load)
Chapter 5. Loading, Linking, and Initializing (oracle.com)
a.找到類(lèi)文件所在的位置---:磁盤(pán)-->類(lèi)裝載器ClassLoader --> 尋找類(lèi)
b.類(lèi)文件的信息交給JVM --> 類(lèi)文件字節(jié)碼流靜態(tài)存儲(chǔ)結(jié)構(gòu) --> JVM里賣(mài)弄的某一塊區(qū)域
c.類(lèi)文件所對(duì)應(yīng)的對(duì)象Class ---> JVM
查找和導(dǎo)入class文件 --> JVM --> 堆
(1)通過(guò)一個(gè)類(lèi)的全限定名獲取定義此類(lèi)的二進(jìn)制字節(jié)流
(2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
(3)在Java堆中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class對(duì)象,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪(fǎng)問(wèn)入口
3.2 鏈接(Link)
3.2.1 驗(yàn)證(Verify):保證被加載類(lèi)的正確性
- 文件格式驗(yàn)證
- 元數(shù)據(jù)驗(yàn)證
- 字節(jié)碼驗(yàn)證
- 符號(hào)引用驗(yàn)證
3.2.2 準(zhǔn)備(Prepare)
為類(lèi)的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值
static int num = 10; // 在準(zhǔn)備階段為num分配內(nèi)存空間,并初始化其值為0
3.2.3 解析(Resolve)
把類(lèi)中的符號(hào)引用轉(zhuǎn)換為直接引用
地址:String str =地址是什么,直接對(duì)應(yīng)到內(nèi)存中某個(gè)地址指向。
3.3 初始化(Initialize)
對(duì)類(lèi)的靜態(tài)變量,靜態(tài)代碼塊執(zhí)行初始化操作
static int num = 10; // 此時(shí),num才會(huì)被真正的賦值為10
3.4 類(lèi)加載機(jī)制圖解
4 類(lèi)裝載器ClassLoader
在裝載(Load)階段,其中第(1)步:通過(guò)類(lèi)的全限定名獲取其定義的二進(jìn)制字節(jié)流,需要借助類(lèi)裝載 器完成,顧名思義,就是用來(lái)裝載Class文件的。
(1)通過(guò)一個(gè)類(lèi)的全限定名獲取定義此類(lèi)的二進(jìn)制字節(jié)流。
4.1 分類(lèi)
1)Bootstrap ClassLoader 負(fù)責(zé)加載$JAVA_HOME中 jre/lib/rt.jar 里所有的class或 Xbootclassoath選項(xiàng)指定的jar包。由C++實(shí)現(xiàn),不是ClassLoader子類(lèi)。
2)Extension ClassLoader 負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目錄下的jar包。
3)App ClassLoader 負(fù)責(zé)加載classpath中指定的jar包及 Djava.class.path 所指定目錄下的類(lèi)和 jar包。
4)Custom ClassLoader 通過(guò)java.lang.ClassLoader的子類(lèi)自定義加載class,屬于應(yīng)用程序根據(jù) 自身需要自定義的ClassLoader,如Tomcat、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader。
4.2 圖解
4.3 加載原則
檢查某個(gè)類(lèi)是否已經(jīng)加載:順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢 查,只要某個(gè)Classloader已加載,就視為已加載此類(lèi),保證此類(lèi)只所有ClassLoader加載一次。
加載的順序:加載的順序是自頂向下,也就是由上層來(lái)逐層嘗試加載此類(lèi)。
雙親委派機(jī)制:
“
定義:如果一個(gè)類(lèi)加載器在接到加載類(lèi)的請(qǐng)求時(shí),它首先不會(huì)自己嘗試去加載這個(gè)類(lèi),而是把 這個(gè)請(qǐng)求任務(wù)委托給父類(lèi)加載器去完成,依次遞歸,如果父類(lèi)加載器可以完成類(lèi)加載任務(wù),就 成功返回;只有父類(lèi)加載器無(wú)法完成此加載任務(wù)時(shí),才自己去加載。
“
優(yōu)勢(shì):Java類(lèi)隨著加載它的類(lèi)加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。比如,Java中的 Object類(lèi),它存放在rt.jar之中,無(wú)論哪一個(gè)類(lèi)加載器要加載這個(gè)類(lèi),最終都是委派給處于模型 最頂端的啟動(dòng)類(lèi)加載器進(jìn)行加載,因此Object在各種類(lèi)加載環(huán)境中都是同一個(gè)類(lèi)。如果不采用 雙親委派模型,那么由各個(gè)類(lèi)加載器自己取加載的話(huà),那么系統(tǒng)中會(huì)存在多種不同的Object 類(lèi)。
“
破壞:可以繼承ClassLoader類(lèi),然后重寫(xiě)其中的loadClass方法,其他方式大家可以自己了解 拓展一下。
5 運(yùn)行時(shí)數(shù)據(jù)區(qū)(Run-Time Data Areas)
在裝載階段的第(2),(3)步可以發(fā)現(xiàn)有運(yùn)行時(shí)數(shù)據(jù),堆,方法區(qū)等名詞
(2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
(3)在Java堆中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class對(duì)象,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪(fǎng)問(wèn)入口
說(shuō)白了就是類(lèi)文件被類(lèi)裝載器裝載進(jìn)來(lái)之后,類(lèi)中的內(nèi)容(比如變量,常量,方法,對(duì)象等這些數(shù) 據(jù)得要有個(gè)去處,也就是要存儲(chǔ)起來(lái),存儲(chǔ)的位置肯定是在JVM中有對(duì)應(yīng)的空間)
5.1 官網(wǎng)概括
Chapter 2. The Structure of the Java Virtual Machine (oracle.com)
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
>>> Java 虛擬機(jī)定義了在程序執(zhí)行期間使用的各種運(yùn)行時(shí)數(shù)據(jù)區(qū)域。 其中一些數(shù)據(jù)區(qū)是在 Java 虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的,只有在 Java 虛擬機(jī)退出時(shí)才會(huì)被銷(xiāo)毀。 其他數(shù)據(jù)區(qū)域是每個(gè)線(xiàn)程。 每線(xiàn)程數(shù)據(jù)區(qū)在創(chuàng)建線(xiàn)程時(shí)創(chuàng)建,在線(xiàn)程退出時(shí)銷(xiāo)毀
5.2 圖解
5.3 常規(guī)理解
5.3.1 Method Area(方法區(qū)):類(lèi)信息、常量、靜態(tài)變量、即使編譯器編譯之后的代碼
“
在JDK1.8中,方法區(qū)存放運(yùn)行時(shí)常量池、方法數(shù)據(jù)、方法的代碼和構(gòu)造方法,包括類(lèi)中的實(shí)例化方法和接口初始化方法。 存放如下數(shù)據(jù):
// 該類(lèi)型數(shù)據(jù)存放在方法區(qū)
public static final CONSTSANT = "constant";
// 方法區(qū)存放方法以及方法的代碼
public class Test {
// 該方法存放在方法區(qū)
public Test() {
}
// 該方法存放在方法區(qū)
public void testMethod() {
}
}
public interface IXXService() {
// 該方法存放在方法區(qū)
default void test () {
}
}
The Java Virtual Machine has a method area that is shared among all Java Virtual
Machine threads.
>> 方法區(qū)只有一個(gè),線(xiàn)程共享的內(nèi)存區(qū)域【線(xiàn)程非安全】,生命周期是跟虛擬機(jī)一樣的。
It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
>> 類(lèi)信息、常量、靜態(tài)變量、即使編譯器編譯之后的代碼。
The method area is created on virtual machine start-up.
Although the method area is logically part of the heap【邏輯上是屬于堆的一部分】, simple implementations may choose not to either garbage collect or compact it.
垃圾回收不太會(huì)討論方法區(qū)的垃圾回收
If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.
>> OOM
方法區(qū)是各個(gè)線(xiàn)程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。
用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻又一個(gè)別名叫做Non-Heap(非堆),目 的是與Java堆區(qū)分開(kāi)來(lái)。
當(dāng)方法區(qū)無(wú)法滿(mǎn)足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常。
“
此時(shí)回看裝載階段的第2步:(2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù) 結(jié)構(gòu)
如果這時(shí)候把從Class文件到裝載的第(1)和(2)步合并起來(lái)理解的話(huà),可以畫(huà)個(gè)圖
(1)方法區(qū)在JDK 8中就是Metaspace,在JDK6或7中就是Perm Space (2)Run-Time Constant Pool
(2)Run-Time Constant Pool
Class文件中除了有類(lèi)的版本、字段、方法、接口等描述信息外,
還有一項(xiàng)信息就是常量池,用于存放編譯時(shí)期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在 類(lèi)加載后進(jìn) 入方法區(qū)的運(yùn)行時(shí)常量池中存放。
“
Each run-time constant pool is allocated from the Java Virtual Machine's method area (§2.5.4).s
每個(gè)運(yùn)行時(shí)常量池都是從 Java 虛擬機(jī)的方法中分配的區(qū)域 (§2.5.4).s
5.3.2 Heap(堆):對(duì)象或者數(shù)組
Java堆是Java虛擬機(jī)所管理內(nèi)存中最大的一塊,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,被所有線(xiàn)程共享。
Java對(duì)象實(shí)例以及數(shù)組都在堆上分配。
The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads.
堆只有一個(gè),線(xiàn)程共享內(nèi)存區(qū)域的【線(xiàn)程非安全】,生命周期跟虛擬機(jī)一樣。
The heap is the run-time data area from which memory for all class instances and arrays is allocated.
存儲(chǔ)數(shù)據(jù)包括:對(duì)象或者數(shù)組
The heap is created on virtual machine start-up.
If a computation requires more heap than can be made available by the automatic storage management system, the Java Virtual Machine throws an OutOfMemoryError.
【如果內(nèi)存不夠,也會(huì)發(fā)生OOM】
此時(shí)回看裝載階段的第3步:(3)在Java堆中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class對(duì)象,作為對(duì)方 法區(qū)中這些數(shù)據(jù)的訪(fǎng)問(wèn)入口
此時(shí)裝載(1)(2)(3)的圖可以改動(dòng)一下
5.3.3 Java Virtual Machine Stacks(虛擬機(jī)棧):局部變量、操作數(shù)、返回?cái)?shù)
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)
【每個(gè)線(xiàn)程獨(dú)有的線(xiàn)程?!?
一個(gè)線(xiàn)程的創(chuàng)建代表一個(gè)棧,每個(gè)方法被當(dāng)前線(xiàn)程調(diào)用了,就代表一個(gè)棧幀。
If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
【StackOverflowError】
If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.
“
經(jīng)過(guò)上面的分析,類(lèi)加載機(jī)制的裝載過(guò)程已經(jīng)完成,后續(xù)的鏈接,初始化也會(huì)相應(yīng)的生效。
假如目前的階段是初始化完成了,后續(xù)做啥呢?肯定是Use使用咯,不用的話(huà)這樣折騰來(lái)折騰去 有什么意義?那怎樣才能被使用到?換句話(huà)說(shuō)里面內(nèi)容怎樣才能被執(zhí)行?比如通過(guò)主函數(shù)main調(diào) 用其他方法,這種方式實(shí)際上是main線(xiàn)程執(zhí)行之后調(diào)用的方法,即要想使用里面的各種內(nèi)容,得 要以線(xiàn)程為單位,執(zhí)行相應(yīng)的方法才行。
那一個(gè)線(xiàn)程執(zhí)行的狀態(tài)如何維護(hù)?一個(gè)線(xiàn)程可以執(zhí)行多少個(gè)方法?這樣的關(guān)系怎么維護(hù)呢?
虛擬機(jī)棧是一個(gè)線(xiàn)程執(zhí)行的區(qū)域,保存著一個(gè)線(xiàn)程中方法的調(diào)用狀態(tài)。換句話(huà)說(shuō),一個(gè)Java線(xiàn)程的運(yùn)行 狀態(tài),由一個(gè)虛擬機(jī)棧來(lái)保存,所以虛擬機(jī)棧肯定是線(xiàn)程私有的,獨(dú)有的,隨著線(xiàn)程的創(chuàng)建而創(chuàng)建。
每一個(gè)被線(xiàn)程執(zhí)行的方法,為該棧中的棧幀,即每個(gè)方法對(duì)應(yīng)一個(gè)棧幀。
調(diào)用一個(gè)方法,就會(huì)向棧中壓入一個(gè)棧幀;一個(gè)方法調(diào)用完成,就會(huì)把該棧幀從棧中彈出。
5.3.4 The pc Register(程序計(jì)數(shù)器)
“
我們都知道一個(gè)JVM進(jìn)程中有多個(gè)線(xiàn)程在執(zhí)行,而線(xiàn)程中的內(nèi)容是否能夠擁有執(zhí)行權(quán),是根據(jù) CPU調(diào)度來(lái)的。
假如線(xiàn)程A正在執(zhí)行到某個(gè)地方,突然失去了CPU的執(zhí)行權(quán),切換到線(xiàn)程B了,然后當(dāng)線(xiàn)程A再獲 得CPU執(zhí)行權(quán)的時(shí)候,怎么能繼續(xù)執(zhí)行呢?這就是需要在線(xiàn)程中維護(hù)一個(gè)變量,記錄線(xiàn)程執(zhí)行到 的位置。
程序計(jì)數(shù)器占用的內(nèi)存空間很小,由于Java虛擬機(jī)的多線(xiàn)程是通過(guò)線(xiàn)程輪流切換,并分配處理器執(zhí)行時(shí) 間的方式來(lái)實(shí)現(xiàn)的,在任意時(shí)刻,一個(gè)處理器只會(huì)執(zhí)行一條線(xiàn)程中的指令。因此,為了線(xiàn)程切換后能夠 恢復(fù)到正確的執(zhí)行位置,每條線(xiàn)程需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器(線(xiàn)程私有)。
如果線(xiàn)程正在執(zhí)行Java方法,則計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;
如果正在執(zhí)行的是Native方法,則這個(gè)計(jì)數(shù)器為空。
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 pcregister 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(本地方法棧)
An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language).
>> Java 虛擬機(jī)的實(shí)現(xiàn)可以使用傳統(tǒng)的堆棧,通俗地稱(chēng)為“C 堆棧”,以支持本地方法(以 Java 編程語(yǔ)言以外的語(yǔ)言編寫(xiě)的方法)
如果當(dāng)前線(xiàn)程執(zhí)行的方法是Native類(lèi)型的,這些方法就會(huì)在本地方法棧中執(zhí)行。






