JVM組成
- Java源代碼編譯成Java Class文件後通過類加載器ClassLoader加載到JVM中
- 類存放在方法區中
- 類創建的對象存放在堆中
- 堆中對象的調用方法時會使用到虛擬機棧,本地方法棧,程序計數器
- 方法執行時每行代碼由解釋器逐行執行
- 熱點代碼由JIT編譯器即時編譯
- 垃圾回收機制回收堆中資源
- 和操作系統打交道需要調用本地方法接口
JVM內存結構
-
程序計數器(通過移位寄存器實現)
- 程序計數器是線程私有的,每個線程單獨持有一個程序計數器
- 程序計數器不會內存溢出
-
虛擬機棧
-
棧:線程運行需要的內存空間
-
棧幀:每一個方法運行需要的內存(包括參數,局部變量,返回地址等信息)
-
每個線程只有一 個活動棧幀(棧頂的棧幀),對應着正在執行的代碼
-
常見問題解析
-
垃圾回收是否涉及棧內存:不涉及,垃圾回收只涉及堆內存
-
棧內存分配越大越好嗎:內存一定時,棧內存越大,線程數就越少,所以不應該過大
-
方法內的局部變量是否是線程安全的:
- 普通局部變量是安全的
- 靜態的局部變量是不安全的
- 對象類型的局部變量被返回了是不安全的
- 基本數據類型局部變量被返回時安全的
- 參數傳入對象類型變量是不安全的
- 參數傳入基本數據類型變量時安全的
-
-
棧內存溢出(StackOverflowError)
-
棧幀過多
- 如遞歸調用沒有正確設置結束條件
-
棧幀過大
- json數據轉換 對象嵌套對象 (用戶類有部門類屬性,部門類由用戶類屬性)
-
線程運行診斷
-
CPU佔用過高(定位問題)
- ‘top’命令獲取進程編號,查找佔用高的進程
- ‘ps H -eo pid,tid,%cpu | grep 進程號’ 命令獲取線程的進程id,線程id,cpu佔用
- 將查看到的佔用高的線程的線程號轉化成16進制的數 :如6626->19E2
- ‘ jstack 進程id ’獲取進程棧信息, 查找‘nid=0X19E2’的線程
- 問題線程的最開始‘#數字’表示出現問題的行數,回到代碼查看
-
程序運行很長時間沒有結果(死鎖問題)
- ‘ jstack 進程id ’獲取進程棧信息
- 查看最後20行左右有無‘Fount one Java-level deadlock’
- 查看下面的死鎖的詳細信息描述和問題定位
- 回到代碼中定位代碼進行解決
-
-
-
-
本地方法棧
- 本地方法棧爲虛擬機使用到的 Native 方法服務
- Native 方法是 Java 通過 JNI 直接調用本地 C/C++ 庫,可以認爲是 Native 方法相當於 C/C++ 暴露給 Java 的一個接口
- 如notify,hashcode,wait等都是native方法
-
堆
-
通過new關鍵字創建的對象都會使用堆內存
-
堆是線程共享的
-
堆中有垃圾回收機制
-
堆內存溢出(OutOfMemoryError)
- 死循環創建對象
-
堆內存診斷
-
命令行方式
- ‘jps’獲取運行進程號
- ‘jmap -heap 進程號’查看當前時刻的堆內存信息
-
jconsole
- 命令行輸入jconsole打開可視化的界面連接上進程
- 可視化的檢測連續的堆內存信息
-
jvisualvm
- 命令行輸入jvisualvm打開可視化界面選擇進程
- 可視化的查看堆內存信息
-
-
-
方法區
- 方法區只是一種概念上的規範,具體的實現各種虛擬機和不同版本不相同
- HotSpot1.6 使用永久代作爲方法區的實現
- HotSpot1.8使用本地內存的元空間作爲方法區的實現(但StringTable還是放在堆中)
- 方法區只是一種概念上的規範,具體的實現各種虛擬機和不同版本不相同
-
StringTable特性
-
常量池中的字符串僅是字符,第一次使用時才變爲對象
-
利用串池機制,避免重複創建字符串
-
字符串常量拼接原理是StringBuilder(1.8)
-
字符串常量拼接原理是編譯器優化
-
StringTable在1.6中存放在永久代,在1.8中存放在堆空間
-
intern方法主動將串池中沒有的字符串對象放入串池
- 1.8中:嘗試放入串池,如果有就不放入,只返回一個引用;如果沒有就放入串池,同時返回常量池中對象引用
- 1.6中:嘗試放入串池,如果有就不放入,只返回一個引用;如果沒有就複製一個放進去(本身不放入),同時返回常量池中的對象引用
-
-
字符串常量池分析(1.8環境)
String s1 = "a";
String s2 = "b";
String s3 = "a"+"b";
String s4 = s1+s2;
String s5 = "ab";
String s6 = s4.intern();
System.out.println(s3==s4);// s3在常量池中,s4在堆上(intern嘗試s4放入常量池,因爲ab存在了就拒絕放入返回ab引用給s6,s4還是堆上的)
System.out.println(s3==s5);// s3在常量池中,s4也在常量池中(字符串編譯期優化)
System.out.println(s3==s6);// s3在常量池中,s6是s4的intern返回常量池中ab的引用,所以也在常量池中
String x2 = new String("c")+new String("d");
String x1 = "cd";
x2.intern();
System.out.println(x1==x2);//x2調用intern嘗試放入常量池,但常量池中已經有cd了,所以只是返回一個cd的引用,而x2還是堆上的引用