Program Counter Register 程式計數器(暫存器)
在物理上:位於暫存器
作用:是記住下一條jvm指令的執行地址
特點:
0: getstatic #20 // PrintStream out = System.out;
3: astore_1 // --
4: aload_1 // out.println(1);
5: iconst_1 // --
6: invokevirtual #26 // --
9: aload_1 // out.println(2);
10: iconst_2 // --
11: invokevirtual #26 // --
14: aload_1 // out.println(3);
15: iconst_3 // --
16: invokevirtual #26 // --
19: aload_1 // out.println(4);
20: iconst_4 // --
21: invokevirtual #26 // --
24: aload_1 // out.println(5);
25: iconst_5 // --
26: invokevirtual #26 // --
29: return
Java Virtual Machine Stacks (Java 虛擬機器器棧)
垃圾回收是否涉及棧記憶體?
棧記憶體並不涉及垃圾回收,棧記憶體的產生就是方法一次一次呼叫產生的棧幀記憶體,而棧幀記憶體在每次方法被呼叫後都會被彈出棧,自動就被回收掉,不需要垃圾回收。來管理
棧記憶體分配越大越好嗎?
不是,線上程不多的情況下,棧記憶體分配大在遞迴時能提高執行速度,但他會影響執行緒的數目,從而影響到整個系統的執行速度
方法內的區域性變數是否執行緒安全?
如果方法內區域性變數沒有逃離方法的作用存取,它是執行緒安全的 如果是區域性變數參照了物件,並逃離方法的作用範圍,需要考慮執行緒安全,
如果是共用的需要考慮執行緒安全,如果是私有的不用考慮執行緒安全
案例1:cpu佔用過多
定位
一些帶有 native 關鍵字的方法就是需要 JAVA 去呼叫原生的C或者C++方法,因為 JAVA 有時候沒法直接和作業系統底層互動,所以需要用到本地方法棧,服務於帶 native 關鍵字的方法。
為原生的方法提供一個執行的空間
Heap 堆
Java 虛擬機器器有一個在所有 Java 虛擬機器器執行緒之間共用的方法區。方法區類似於傳統語言的編譯程式碼的儲存區,或者類似於作業系統程序中的「文字」段。它儲存每個類的結構,例如執行時常數池、欄位和方法資料,以及方法和建構函式的程式碼,包括類和範例初始化以及介面初始化中使用 的特殊方法。
方法區是在虛擬機器器啟動時建立的。儘管方法區在邏輯上是堆的一部分,但簡單的實現可能會選擇不進行垃圾收集或壓縮它。本規範不要求方法區域的位置或用於管理已編譯程式碼的策略。方法區域可以是固定大小,也可以根據計算需要擴大,如果不需要更大的方法區域,可以縮小。方法區的記憶體不需要是連續的。
Java 虛擬機器器實現可以為程式設計師或使用者提供對方法區域初始大小的控制,以及在方法區域大小可變的情況下,對最大和最小方法區域大小的控制。
以下異常情況與方法區相關:
如果方法區域中的記憶體無法滿足分配請求,Java 虛擬機器器將丟擲一個OutOfMemoryError.
JVM規範-方法區定義
1.8 以前會導致永久代記憶體溢位
演示永久代記憶體溢位 java.lang.OutOfMemoryError: PermGen space
-XX:MaxPermSize=8m
1.8 之後會導致元空間記憶體溢位
演示元空間記憶體溢位 java.lang.OutOfMemoryError: Metaspace
-XX:MaxMetaspaceSize=8m
場景:
spring
mybatis
// 二進位制位元組碼(類基本資訊,常數池,類方法定義,包含了虛擬機器器指令)
public class Test {
public static void main(String[] args) {
System.out.println("hello world");
}
}
然後使用 javap -v Test.class 命令反編譯檢視結果:
每條指令都會對應常數池表中一個地址,常數池表中的地址可能對應著一個類名、方法名、引數型別等資訊。
面試題:
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; // ab
String s4 = s1 + s2; // new String("ab")
String s5 = "ab";
String s6 = s4.intern();
// 問
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // true
String x2 = new String("c") + new String("d"); // new String("cd")
x2.intern();
String x1 = "cd";
// 問,如果調換了【最後兩行程式碼】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2);
// jdk1.6:
// String x1 = "cd"; x2.intern();
// x2.intern(); false String x1 = "cd"; ture
// jdk1.8:
// String x1 = "cd"; x2.intern();
// x2.intern(); false String x1 = "cd"; ture
練習:
// StringTable [ "a", "b" ,"ab" ] hashtable 結構,不能擴容
public class Demo1_22 {
// 常數池中的資訊,都會被載入到執行時常數池中, 這時 a b ab 都是常數池中的符號,還沒有變為 java 字串物件
// ldc #2 會把 a 符號變為 "a" 字串物件
// ldc #3 會把 b 符號變為 "b" 字串物件
// ldc #4 會把 ab 符號變為 "ab" 字串物件
public static void main(String[] args) {
String s1 = "a"; // 懶惰的
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString() new String("ab")
String s5 = "a" + "b"; // javac 在編譯期間的優化,結果已經在編譯期確定為ab
System.out.println(s3 == s4);//s3是在串池中的,而s4則是在堆中,所有不相等
System.out.println(s3 == s5);// true
}
}
使用javap -v Demo1_22.class命令
jdk1.6 StringTable 位置是在永久代中,1.8 StringTable 位置是在堆中。
-Xmx10m 指定堆記憶體大小
-XX:+PrintStringTableStatistics 列印字串常數池資訊
-XX:+PrintGCDetails
-verbose:gc 列印 gc 的次數,耗費時間等資訊
演示StingTable垃圾回收:
public static void main(String[] args) throws InterruptedException {
int i = 0;
try {
for (int j = 0; j < 100000; j++) { // j=100, j=10000
String.valueOf(j).intern();
i++;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(i);
}
}
調整 -XX:StringTableSize=桶個數
考慮將字串物件是否入池
Direct Memory
檔案讀寫過程(IO):
因為 java 不能直接操作檔案管理,需要切換到核心態,使用本地方法進行操作,然後讀取磁碟檔案,會在系統記憶體中建立一個緩衝區,將資料讀到系統緩衝區, 然後在將系統緩衝區資料,複製到 java 堆記憶體中。缺點是資料儲存了兩份,在系統記憶體中有一份,java 堆中有一份,造成了不必要的複製。
使用了 DirectBuffer 檔案讀取流程:
直接記憶體是作業系統和 Java 程式碼都可以存取的一塊區域,無需將程式碼從系統記憶體複製到 Java 堆記憶體,從而提高了效率。