JVM 運行時的數據區
.java 源代碼通過編譯成.class 字節碼文件,將字節碼文件運行在JVM中。接下來一起探討JVM運行時都有哪些數據區域。先上一張結構圖:
線程共享
線程共享:每個線程都能訪問這塊內存數據,隨着虛擬機或者GC而創建和銷燬。
由圖可知,線程共享分爲:方法區和堆內存。
方法區
方法區:用來存儲加載類信息,常量,靜態變量,編譯後的代碼等數據。
方法區存放着類的版本,字段,方法,接口和常量池。
堆內存
堆內存:JVM啓動時創建,存放對象的實例。垃圾回收器主要就是管理堆內存。如果滿了就會出現OOM。細分爲:老年代,新生代,(Eden,From Survivor,To Suvivor。
線程獨佔
線程獨佔:每個線程都會有它獨立的空間,隨生命週期而創建和銷燬。
由圖可知,線程獨佔分爲:虛擬機棧,本地方法棧,程序計數器。
虛擬機棧
虛擬機棧:爲虛擬機執行Java方法而準備的
每個線程都在這個空間有一個私有的空間,由多個棧幀組成(Stack Frame)組成
一個線程會執行一個或多個方法,一個方法對應一個棧幀。
棧幀內容包含:局部變量表,操作數棧,動態鏈接,方法返回地址,附加信息
棧內存默認最大1M,超出則拋出StackOverFlowError。
本地方法棧
本地方法棧:爲虛擬機執行Native 本地方法而準備的
虛擬機規範沒有規定具體的實現,由不同的虛擬機廠商去實現。
HotSpot虛擬機中虛擬機棧和本地方法棧的實現方式是一樣的,同樣超出大小以後也會拋出StackOverFlowError。
程序計數器(Program Counter Register)
程序計數器:用來記錄當前線程執行字節碼的位置,存儲的字節碼指令地址,如果執行Native方法,則計數器爲空。每個線程都在這個空間有一個私有空間,佔用內存空間很少。
CPU同一時間,只會執行一條線程中的指令,JVM多線程會輪流切換並分配CPU執行時間的方式,爲了線程切換後,需要通過程序計數器來恢復正確的執行位置。
案例一分析
代碼示例
public class Demo1 {
public static void main(String[] args) {
int x = 500;
int y = 100;
int z = x / y;
System.out.println(z);
}
}
在文件所在目錄執行編譯及其反編譯命令:
javac Demo1.java
javap -v Demo1.class
得到Demo1對應的指令碼:
運行過程分析
1)將主函數中的args放入本地變量表中,程序開始執行:
2)將500 壓入操作數棧,再同步到本地變量表中,程序計數器記錄代碼執行到第3行
3)同樣的操作,代碼執行到第6行:
4)將x/y的結果放到本地變量表中,代碼執行到第10行:
5) 執行完畢,返回結果
案例二分析
代碼示例
public class Demo2 {
public static void main(String[] args) {
int j = 0;
for (int i = 0; i < 10; i++) {
j = j++;
}
System.out.println(j);
}
}
同樣執行編譯和反編譯查看指令碼:
javac Demo2.java
javap -v Demo2.class
運行過程分析
1) 初始化變量j
2) 初始化for循環中的變量i:
3) 執行for循環初始化:
4)開始循環:
5)循環結束,返回結果爲0:
在程序計數器執行到11行的時候,對於JVM來說,先把j = 0 的值壓入到了操作數棧,,而在本地變量表中執行自增,但是操作數棧中還是原來的值。因此得到最後j的值始終是0;