這篇文章以java爲例講解java源碼到字節碼到字節碼對象再到jvm的內存中整個過程爲例。從而來理解JVM的原理。JVM由ClassLoader,JVM運行時數據區,執行引擎三部分構成。這篇文章主要會講解JVM運行時數據區的各個分區的內在特點以及各個分區之間的關係。
一,JVM簡介
JVM(Java Virtual Machine)也就是java虛擬機。用來運行字節碼碼的。可以運行Java,kotlin,Scala,Clojure,Groovy,Jython,JRuby,Ceylon,Eta,Haxe 10餘種語言編譯的字節碼。
後面就以Java來講解。先給上一張圖。可以看出JVM是在這個JDK中最底層的一小部分。但是他卻起到到來了極其重要的作用。
java源碼通過javac編譯成.class的字節碼文件然後在由java虛擬機運行。java具有垮平臺的特點。它跨平臺就是通過不同的操作系統上裝上不同的JDK從而擁有不同的JVM來實現的跨平臺。也就是一份編譯出來的字節碼文件可以在不同的操作系統的JVM上運行。
JVM由ClassLoader,JVM運行時數據區,執行引擎三部分構成。
下面是官方圖:
下面來一箇中文的簡圖,文章主要講解運行時數據區的內存空間的分區,每個分區的內在特點,以及分區間的相互關係。
二,方法區
說到方法區就要提到 元空間,永久帶 。方法區是規範,元空間,永久帶是具體實現。
- 永久帶是jdk8之前方法區的具體實現。他是放在堆裏面的,所有他也會出現oom(out of memory),也會觸發GC。
- 元空間是jdk8以後方法區的具體實現。他是放在直接內存中的。也就是操作系統內存。所有會出現native memory
1,元空間的默認大小是20.75M。最大內存就是系統內存。(目前系統64位,其中16位爲保留位,最大內存就爲2的48次方=256T)
2,元空間調優
(1)元空間最大最小設置成一樣。這樣可以防止內存抖動。
(2)元空間設置爲多物理內存的1/32。具體多少需要進行調試。
(3)設置元空間的時候比起實際使用大小預留20%到30%這樣比較安全。
方法區裏面存放了:常量,靜態變量,類信息。
三,程序計數器
程序計數器是指向當前線程執行的字節碼指令的(地址)行號。
在cmd窗口執行命令 javap -c Test.class >Test.txt
打開Test.txt文件。程序計數器就是存放這個行號的。
Compiled from "Test.java"
public class AAA.Test {
public AAA.Test();
Code:
0: aload_0
1: invokespecial #12 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #3 // class java/lang/Object
8: dup
9: invokespecial #12 // Method java/lang/Object."<init>":()V
12: putfield #14 // Field object:Ljava/lang/Object;
15: return
public int add();
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 100
9: imul
10: istore_3
11: iload_3
12: ireturn
public static void main(java.lang.String[]);
Code:
0: new #1 // class AAA/Test
3: dup
4: invokespecial #28 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #29 // Method add:()I
12: istore_2
13: getstatic #31 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_2
17: invokevirtual #37 // Method java/io/PrintStream.println:(I)V
20: return
}
四,虛擬機棧(線程棧)
虛擬機棧裏面存放的是棧幀,一個方法對應一個棧幀。
棧幀中存放了:局部變量表,操作數棧,動態鏈接,方法出口。
- 局部變量表:就是這個方法類的局部變量彙總。
- 操作數棧:這些局部變量在賦值給變量引用之前要先壓入操作數棧。以及進行計算的時候臨時存放變量的棧。
- 動態鏈接:就是存放這個方法在方法區內的內存地址。
- 方法出口:就是返回現場。比如在執行到main方法的第9行指令開始調用add()方法,這個時候就會把這個行號存到方法出口中,當add()方法執行結束然後就回到main()方法中執行第10行指令。
一個方法執行完JVM需要做的事情。
- 1,恢復局部變量表指針
- 2,恢復操作數棧指針
- 3,恢復程序技術器。
- 4,如果方法有返回地址,需要返回。
- 5,清理棧幀。(程序計數器去完成的)
這個是上面圖對應的java代碼
public class Test {
public int add() {
int a = 1;//局部變量
int b = 2;//局部變量
int c = (a+b)*100;
return c;
}
public static void main(String[] args) {
Test text=new Test();
int result = text.add();
System.out.println(result);
}
}
五,堆
1,堆是用來存放對象的地方
2,堆的默認大小
- 最小是是物理內存的1/64
- 最大是1/4
3,堆內存分區
- yang(年輕代)和old(老年代)兩部分。默認比例爲1:2
- yang(年輕代)又分爲Eden(伊甸園區)和survivor0區,survivor1區。默認比例爲8:1:1
六,本地方法棧
本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。與虛擬機棧一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。
七,個內存區之間的關係
虛擬機棧-->方法區 動態鏈接。(虛擬機棧中的棧幀對應的方法在方法區的內存地址)
堆-->方法區 關聯是class Pointer(也就是對象執行所對應的類的Class對象所屬地址)
方法區-->堆 類裏面的靜態對象引用。(例如類裏面的public static Test test = new Test();)