簡述
"Write Once,Run Anywhere"(一次編譯,到處執行)是sun宣傳Java語言所提出的口號。Java語言跨平臺的特性與Java虛擬機器的存在密不可分。Java原始碼通過編譯生成.class檔案位元組碼後再被JVM解釋轉化為目標機器程式碼,到處執行的關鍵與前提就是JVM。所以並不是Java語言本身可以可以跨平臺,而是在不同的平臺都有可以讓Java語言執行的環境而已。在可以執行Java虛擬機器的地方都內含一個JVM作業系統,從而使JAVA提供了各種不同平臺上的虛擬機器制,由此可見匯出執行的關鍵就是JVM。
JVM記憶體區域
JVM結構
執行緒共享區域
②堆中存放物件例項,幾乎所有的物件例項都在這裡分配記憶體,所以是GC主要區域。
③不需要連續的記憶體
④若堆中沒有記憶體完成例項分配,則會丟擲OutOfMemoryError異常
②執行時常量池是方法區的一部分
③這區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝
④不需要連續的記憶體,方法區gc價效比較低,gc頻率遠沒有堆中高
⑤若方法區無法滿足記憶體分配需求時,則會丟擲OutOfMemoryError異常
執行緒隔離區域(隨執行緒而生,隨執行緒而滅)
②方法執行建立棧幀(棧幀所需記憶體在類結構確定下來時就被已知,不考慮JIT優化)用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊
③棧的大小決定了方法呼叫的深度(遞迴多少層次,或巢狀呼叫多少層其他方法)
④若執行緒請求的棧深度大於虛擬機器所允許的深度,則丟擲StackOverflowError異常
⑤若棧中無憂記憶體可分配,則丟擲OutOfMemoryError異常
②同樣也會出現StackOverflowError、OutOfMemoryError異常
②此區域不會出現OutOfMemoryError異常
OOM
不斷建立物件
/**
* VM Args:-Xmx10m
*/
public static void main(String[] args) {
List list = new ArrayList();
while (true){
list.add(new Object());
}
}
複製程式碼
結果:
java.lang.OutOfMemoryError: Java heap space
StackOverflowError異常:
遞迴呼叫
private void test(){
test();
}
public static void main(String[] args) {
new StackTest().test();
}
複製程式碼
結果:
java.lang.StackOverflowError
OutOfMemoryError異常:
/**
* VM Args:-Xss2M
* @author zzm
*/
public class JavaVMStackOOM {
private void dontStop(){
while (true){
}
}
public void stackLeakByThread(){
while(true){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
複製程式碼
本人執行了一下果然像書(深入理解Java虛擬機器)中所說電腦重啟了,Windows系統的同學謹慎執行
/**
* VM Args:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M (jdk8)
* -XX:PermSize=10M -XX:MaxPermSize=10M(jdk7)
* @author zzm
*/
public class Test {
static class OOMOjbect{}
public static void main(String[] args) {
while(true){
Enhancer eh = new Enhancer();
eh.setSuperclass(OOMOjbect.class);
eh.setUseCache(false);
eh.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object arg0, Method arg1,
Object[] arg2, MethodProxy arg3) throws Throwable {
return arg3.invokeSuper(arg0, arg2);
}
});
eh.create();
}
}
}
複製程式碼
本人用的是jdk8,輸出結果:
java.lang.OutOfMemoryError: Metaspace
物件的記憶體佈局
物件在記憶體中佈局可以分成三塊區域:物件頭、例項資料和對齊填充
物件頭
物件頭包括兩部分資訊:
儲存內容 | 標誌位 | 狀態 |
物件雜湊碼、物件分代年齡 | 01 | 未鎖定 |
指向鎖記錄的指標 | 00 | 輕量級鎖定 |
指向重量級鎖的指標 | 10 | 膨脹(重量級鎖定) |
空,不需要記錄資訊 | 11 | GC標記 |
偏向執行緒ID、偏向時間戳、物件分代年齡 | 01 | 可偏向 |
例項資料
例項資料就是在程式程式碼中所定義的各種型別的欄位,包括從父類繼承的,這部分的儲存順序會受到虛擬機器分配策略引數和欄位在原始碼中定義順序的影響
對齊填充
並不是必然存在的,沒有特別的含義。由於HotSpot的自動記憶體管理要求物件的起始地址必須是8位元組的整數倍,即物件的大小必須是8位元組的整數倍,物件頭的資料正好是8的整數倍,所以當例項資料不夠8位元組整數倍時,需要通過對齊填充進行補全。
物件訪問
控制程式碼
使用控制程式碼訪問的話,Java堆中將會劃分出一塊記憶體來作為控制程式碼池,reference中儲存的就是物件的控制程式碼地址,而控制程式碼中包含了物件例項資料與型別資料各自的具體地址資訊:
直接指標
使用直接指標訪問的話,Java堆物件的佈局中就必須考慮如何放置訪問型別資料的相關資訊,reference中儲存的直接就是物件地址
這兩種物件訪問方式各有優勢:
oracle JDK官方預設虛擬機器HotSpot採用第二中方式進行物件訪問
感謝
《極客時間——楊曉峰Java核心技術36講第一講》
《深入理解Java虛擬機器》