一、JVM的運行過程
- JVM虛擬機我們可以把它當做一臺虛擬出來的計算機,也有自己的內存管理如:堆、棧、方法區等。
-
JVM的作用是將字節碼翻譯成不同操作系統可以識別的機器碼執行。
運行過程如下圖:
(1)我們寫的一個JAVA程序首先通過JDK的中JAVAC工具進行編譯,編譯成了.class文件,也就是我們說的字節碼。
(2)JAVA類加載器(ClassLoader)將字節碼載入到JVM的運行時數據區,也就是JVM中的一塊內存區域。
(3)通過執行引擎調用操作系統接口解釋執行或者JIT執行。
- 解釋執行指的是JVM通過加載到的字節碼進行翻譯執行
- JIT指的是將熱點代碼直接翻譯成機器碼保存下來,方便下次直接執行。
區別是:前者啓動快,但是運行速度慢,因爲每次都要邊解釋邊執行。而後者啓動速度慢,但是執行速度快,因爲需要將熱點代碼翻譯成機器碼保存下來,但是下次執行到熱點代碼的時候直接通過執行之前保存的機器碼,因此速度比較快。 - 一般虛擬機是通過兩種方式混合使用。
二、運行時數據區
字節碼通過類加載器加載到了JVM運行時數據區,而運行時數據區又將內存劃分了不同的區域。不同的身份將進入到不同的區域。如下圖:
運行時數據區主要分爲兩大塊:線程共享數據區和線程隔離數據區
其中右邊白色部分包括虛擬機棧、本地方法棧、程序計數器是線程隔離數據區。左邊灰色區域方法區、堆是線程共享數據區。
- 線程隔離數據區
指的是每一個線程都擁有自己的一塊內存區域,就比如有兩個線程A和B,他們各自擁有自己的虛擬機棧、本地方法棧、程序計數器。 - 線程共享區
指的是線程共享的區域,每個線程都共享了方法區和堆
1、線程共享數據區
1.1、程序計數器
指向了當前線程正在執行的字節碼指令地址,換句話說就是記錄當前字節碼指令執行的位置。因爲在操作系統中由於時間片輪轉機制,當前的線程可能指令還沒執行完就被切出去了,因此要通過程序計數器記錄正在執行的位置,當線程重新獲得時間片之後,在原來的位置繼續執行。
1.2、虛擬機棧
存儲當前線程運行方法所需要的數據、指令、返回地址。我們可以理解成一個線程就擁有一個虛擬機棧,而線程中的每一個方法就是一個棧幀。
1.2.1、棧幀
棧幀中又包含了:局部變量表、操作數棧、動態鏈接、 完成出口(返回地址)
如下圖
線程中一個方法的執行就是在這些身份相互配合執行。我們將線程隔離數據區進行更細的劃分如下圖
我們看到一個虛擬機棧會有多個棧幀,每一個棧幀入棧出棧的過程就是一個方法執行的過程。棧幀中的局部變量表、操作數棧、動態鏈接、完成出口存放了方法執行過程的數據。下面我們來演示一個方法的執行對應字節碼指令的執行。
- JAVA源代碼
package com.it.test;
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.function();
}
public int function() {
int a = 1;
int b = 3;
int c = (a + b) * 10;
return c;
}
}
- 反彙編後的字節碼
D:\app_work_space\JavaHighSets\src\com\it\test>javap -c Test.class
Compiled from "Test.java"
public class com.it.test.Test {
public com.it.test.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/it/test/Test
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method function:()I
12: pop
13: return
public int function();
Code:
0: iconst_1
1: istore_1
2: iconst_3
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: iload_3
12: ireturn
}
我們將源代碼和反彙編後的代碼合在一起進行對比,然後分析function方法的執行如何在虛擬機棧和程序計數器中體現的。
//源代碼
public int function() {
int a = 1;
int b = 3;
int c = (a + b) * 10;
return c;
}
//反彙編後的代碼
public int function();
Code:
0: iconst_1
1: istore_1
2: iconst_3
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: iload_3
12: ireturn
}
- code就是代碼的意思
- 0、1、2...等等行號,我們可以認爲是字節碼指令執行到的位置,程序計數器存放的就是這些數據的地址,記錄當前方法執行到的位置,因此程序計數器存放的數據較小,不會因爲內存不足產生OOM異常。
0: iconst_1
將int 類型 1壓入到操作數棧
1:istore_1
將操作數棧棧頂出棧,存入局部變量表下標爲1的位置
以上兩個步驟完成了int a =1;
2: iconst_3
將int類型 3壓入到操作數棧
3:istore_2
將操作數棧棧頂出棧,存入局部變量表下標爲2的位置
以上兩個步驟完成了int b = 3;
4:iload_1
將局部變量表下標爲1的位置的數據壓入操作數棧
5:iload_2、
將局部變量表下標爲2的位置的數據壓入操作數棧
6: iadd
三部曲,
(1)將棧頂的兩個數據出棧
(2)相加
(3)將結果壓入到操作數棧
7: bipush
將int類型10壓入到操作數棧
9:
imul三部曲
(1)將棧頂兩個數據出棧
(2)相乘
(3)結果壓入到操作數棧
10:istore_3
將棧頂數據存入到局部變量表下標爲3的位置
11: iload_3
將局部變量表下標爲3的位置的數據存到操作數棧,作爲返回值
以上就完成了int c = (a + b) * 10
12: ireturn
將操作數棧棧頂的數據出棧返回
完成了rerturn z
最後方法執行完畢之後,棧幀就從Java虛擬機棧中出棧
1.3、Java虛擬機棧小結
Java虛擬機棧存放的是一個方法的執行過程、而每一個方法對應一個棧幀,每一個方法的執行過程就是棧幀入棧出棧的過程,而棧幀中又包含了局部變量表、操作數棧、動態鏈接、完成出口(返回地址)。這四個角色用於存放執行過程的數據。
(1)局部變量表
存方法內部的局部變量基本數據類型、以及局部對象類型變量的引用
(2)操作數棧
完成方法中數據的操作
(3)動態鏈接
Java語言中會有多肽,例如以下代碼,Student和Teacher都繼承了User,執行方法eat,在編譯期間是沒法知道是執行Student的eat方法還是Teacher的eat方法,所以在方法運行期間,Java虛擬機棧的棧幀中存放一個動態鏈接來確定執行誰的eat方法
public class User {
public void eat(){
}
public static void main(String[] args) {
User user = new Student();
user.eat();
user = new Teacher();
user.eat();
}
}
(4) 完成出口(返回地址)
例如以下代碼, 在main方法中執行了function方法,當function放執行完畢之後,要回到main方法中繼續執行,那具體要回到的地方是哪裏呢?就是我們 System.out.println("你好。。。");這一行這裏作爲出口,所以這個出口就是存放在棧幀中的“完成出口”區域。
package com.it.test;
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.function();
System.out.println("你好。。。");
System.out.println("哈哈。。。");
}
public int function() {
int a = 1;
int b = 3;
int c = (a + b) * 10;
return c;
}
}
1.4、本地方法棧
以上我們說到了線程隔離區中的程序計數器、虛擬機棧。下面我們來了解一下最後一個本地方法棧。
我們知道虛擬機棧存的是Java方法的執行過程所需的指令、數據、返回地址等,每一個方法的執行就是一個棧幀入棧出棧的過程。而本地方法的執行則對應了我們的本地方法棧。例如我們的hashCode方法就是本地方法。
public native int hashCode();
當JVM創建的線程調用了native方法之後,JVM不會爲其在虛擬機棧中創建棧幀,而是簡單的動態鏈接並直接調用native方法。
一般的虛擬機,Java虛擬機棧和本地方法棧都是合在一塊區域。例如我們的HotSpot。