JVM的記憶體結構
- 程式計數器
- 虛擬機器棧
- 本地方法棧
- 堆
- 方法區
程式計數器
二進位制位元組碼-->直譯器-->機器碼-->CPU
作用
記錄下一條JVM指令的執行地址
特點
- 執行緒私有的(每個執行緒有自己的程式計數器):java支援多執行緒執行,CPU給每個執行緒分配一個時間片,如果在時間片內沒執行完,去執行另一個執行緒在執行該程式。執行緒切換的過程中,將記錄執行緒一應該執行的下一條程式碼
- 不會存在記憶體溢位
棧
虛擬機器引數
-Xss+記憶體大小
特點
先進後出(例如:手槍彈夾,羽毛球桶)
定義
- 虛擬機器棧:每個執行緒執行需要的記憶體空間
- 棧幀: 每個方法執行時需要的記憶體
- 每個執行緒由多個棧幀做成,對應著方法呼叫時佔用的記憶體(方法內呼叫別的方法)
- 每個執行緒只能有一個活動棧幀,對應著當前正在執行的方法
執行完對應的方法後,棧幀出棧,將對應的記憶體釋放掉
問題辨析
-
垃圾回收是否涉及棧記憶體?
不需要,方法(棧幀)在執行後會被自動彈出棧 -
棧記憶體分配越大越好嗎?
不是,棧記憶體越大,執行緒越少(機械記憶體固定10M,一個棧2M則可以執行5個執行緒;一個棧1M,則可執行10個執行緒) -
方法內的區域性變數是否安全?(多個執行緒對這個變數是共享的還是這個變數對每個執行緒是私有的)
不會,每個執行緒各有一個棧,各自的棧裡各有一個棧幀儲存x,互不干擾。
如果方法內區域性變數沒有逃離方法的作用訪問,他是執行緒安全的
如果是區域性變數引用了物件,並逃離方法的作用範圍,需要考慮執行緒安全
記憶體溢位
- 棧幀過多導致棧記憶體溢位(遞迴死迴圈)
- 棧幀過大(不太容易出現)
執行緒診斷
- 案例一:CPU佔用過多(需要Linux作業系統)
- 用top定位哪個程序對cpu的佔用過高
- ps H -eo pid,tid,%cpu|grep程序id(用ps命令進一步定位哪個執行緒引起cpu佔用過多)
- jstack程序(可以根據執行緒id找到有問題的執行緒,進一步定位問題程式碼的原始碼行號)
- 案例二: 程式執行很長時間沒有結果(需要Linux作業系統)
本地方法棧
在呼叫本地方法是提供的記憶體空間(本地方法:不是由java程式碼編寫的方法,Java程式碼的限制,底層的程式碼需要c/c++實現的本地方法。如:object類的clone()方法)
堆
虛擬機器引數
-Xmx+記憶體大小
Heap堆
透過new關鍵字,常見物件都會使用堆記憶體
特點
- 它是執行緒共享的,堆中物件都需要考慮執行緒安全性問題
- 有垃圾回收機制
堆記憶體溢位
String a = "hello";
List<String> list = new ArrayList<>();
while(true){
list.add(a);
a = a + a;
}
list:hello / hellohello / hellohellohello...
堆記憶體診斷
- jps工具:檢視當前系統中有哪些java程序
- jmap工具:檢視堆記憶體佔用情況
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("1....");
Thread.sleep(30000);
byte[] array = new byte[1024 * 1024 * 10];
System.out.println("2....");
Thread.sleep(30000);
array = null;
System.gc();
System.out.println("3....");
Thread.sleep(1000000L);
}
}
執行以上程式碼開啟控制檯選擇Terminl(命令列介面)
在“1...”列印出後輸入jps
在“2...”列印出後輸入jhsdb(看jdk,有的不需要) jmap -heap 45984(你執行的程式前面的代號)
在“3...”列印出後輸入jhsdb(看jdk,有的不需要) jmap -heap 45984(你執行的程式前面的代號)
觀察比較堆記憶體的使用情況
- jconsole工具:圖形介面的多功能監測工具,可以連續監測
執行以上程式碼開啟控制檯選擇Terminl(命令列介面)輸入jconsole,回車會跳出Java監控和管理控制檯,選擇你執行的程式連線-->不安全的;連線觀察記憶體佔用情況
案例
- 垃圾回收後,記憶體佔用仍然很高
使用jvirsualvm(視覺化的形式展示虛擬機器,在控制檯使用)