1. JVM(Hotspot)內存模型簡介
如圖所示,JVM的內存模型中主要涵蓋了以下5個部分.
1.1 程序計數器
程序計數器主要存儲每個線程執行的字節碼指令的行號.
線程私有.
Java方法中的代碼經過javac處理後,在class文件中以Code屬性表形式存在,表中以數值方式存儲了相應的字節碼指令.
當線程執行native方法時,存儲的值爲undefined.該區域不會出現OOM.
1.2 虛擬機棧
VM棧中以棧幀的形式存儲了線程執行Java方法的信息.VM棧只有棧幀的入棧和出棧兩種動作.
線程私有.
1.3 本地方法棧
Native棧的作用和VM棧類似,當線程執行了非Java方法(如C實現)時,會在內存中開闢一份Native棧,用以存儲改方法的執行信息.
線程私有.
Hotspot中,VM棧和Native棧已經合二爲一.
1.4 方法區
方法區用以儲存常量,靜態變量,類的符號引用等信息.
各個線程公有.
Hotspot中將垃圾回收分代的思想引申到了方法區,所以Hotspot中的方法區也就是持久帶.
1.5 堆
堆內存的唯一作用就是存儲對象實例.
各線程公有.
2. 代碼演示內存溢出異常
2.1 堆溢出
-
-
public class JVMTest {
static class OOMTest {
};
public static void main(String[] args) {
/**
* 模擬堆溢出
*/
List<OOMTest> list = new ArrayList<OOMTest>();
while (true) {
list.add(new OOMTest());
}
}
}
對象創建過多導致堆內存不足.
2.2 方法區溢出
public class JVMTest {
static class OOMTest {
};
public static void main(String[] args) {
/**
* 模擬方法區溢出
*/
List<String> list = new ArrayList<String>();
int i=0;
while(true){
list.add(String.valueOf(i++).intern());
//String類的intern方法創建的字符串,不會在編譯期而是在運行期放入方法區的常量池
}
}
}
常量池中字符串過多導致方法區內存不足.
2.3 VM棧溢出
public class JVMTest3 {
private int count = 1;
public void stackTest() {
count++;
stackTest();
}
public static void main(String[] args) throws Throwable {
JVMTest3 test = new JVMTest3();
try {
test.stackTest();
} catch (Throwable err) {
System.out.println("Stack length:" + test.count);
throw err;
}
}
}
方法調用層次過多導致棧深過大.
3.其他
什麼是JIT即時編譯
Java的編譯通常指由源文件編譯成字節碼的過程.
但是,字節碼是可以在不同平臺運行的,由字節碼指令轉化到機器指令的過程需要運行時進行.
trictfp關鍵字
此關鍵字可以修飾類,接口,方法.
被strictfp修飾後,內部的所有運算嚴格遵守IEEE754規範,不會應爲運行在不同平臺上產生不同的結果.
各版本jdk的一些新特性
Jdk1.4:
真正走向成熟的版本.
Jdk1.5
增加了泛型,枚舉等特性.
Java.util.concurrent併發包等.
Jdk1.6:
在同步鎖,垃圾收集等方面做了一些改進.
Jdk1.7:
提供了G1收集器
Jdk1.8:
G1收集器爲jvm的默認收集器.
什麼是常量池,存在於內存哪個部分,存儲什麼
class文件常量池:存放編譯器生成的字面量和符號引用
運行時常量池:存儲運行期生成的常量,如String.intern().
都屬於方法區.
查看class文件中常量池信息
javap -verbose ../JVMTest4.class
什麼是直接內存
使用native函數庫直接分配的對外內存,與NIO類相關.
常見JVM的設置參數有哪些
-Xms 初始堆大小
-Xmx 最大堆大小
-Xmn 年輕代大小
-XX:PermSize 初始永久代大小,默認物理內存1/64
-XX:MaxPermSize 最大永久代大小,默認物理內存1/4
-Xss 單線程棧大小,棧深不是很大128k足夠.
永久代與方法區的區別
永久代只是hotspot將分代回收的理念引申到了方法區,
且在jdk1.8之後,不存在永久代的概念,用metaspace實現了方法區.
jdk1.6,1.7,1.8中方法區的遷移過程,String.intern()在1.6和1.7中有什麼不同
1.6中,使用永久代實現了方法區
1.7中,將運行時常量池從方法區移至堆中.
1.8中,不存在永久代,使用metaspace實現了方法區.
如以下代碼:
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
Jdk1.6:
1)常量池中創建”1”對象,堆中兩個new String()對象,存儲常量池中”1”的引用.堆中創建相加後的對象(內容爲”11”),s3存儲這個對象的引用.
2)s3.intern()執行時,去常量池中尋找”11”,沒找到則在常量池中創建”11”.
3)S4在常量池中找到”11”,返回引用.
4)所以,結果爲false.
Jdk1.7
1)常量池中創建”1”對象,堆中兩個new String()對象,存儲常量池中”1”的引用.堆中創建相加後的對象(內容爲”11”),s3存儲這個對象的引用
2)s3.intern()執行時,去常量池中尋找”11”,沒找到,則將堆中相加後對象的引用存儲在常量池中.
3)S4獲取到的爲對中內容爲”11”的引用,和s3的內容一致
4)結果爲true