第二章 Java內存區域與內存溢出異常html
一、運行時數據區域java
程序計數器:git
- 當前線程所執行的字節碼的行號指示器,用於存放下一條須要運行的指令。
- 運行速度最快位於處理器內部。
- 線程私有。
虛擬機棧:github
- 描述的是Java方法執行的內存模型,用於存放對象的引用和基本數據類型。
- 每一個方法執行的時候都會建立一個棧幀(stack frame)用於存放 局部變量表、操做棧、動態連接、方法出口。
- 線程私有,生命週期與線程相同。
方法棧:安全
- 和虛擬機棧功能相似,管理本地的方法調用。
- 虛擬機棧爲虛擬機執行的Java方法的方法服務,方法棧則爲虛擬機使用的本地方法的服務。
堆:app
- 虛擬機最大的一塊區域,虛擬機啓動的時候建立。
- 用於存放對象的實例,全部對象實例和數據的在堆上分配內存。
- 線程共享的區域。
方法區:jvm
- 用於存放一些類信息,常量,靜態變量和即時編譯後的代碼等數據。
- 線程共享的區域。
運行時常量池:ide
- 方法區的一部分,用於存放編譯期生成的各類字面量和符號引用。
直接內存:佈局
- 不屬於運行時數據區,堆外內存。
二、HotSpot虛擬機對象探祕post
對象的建立:
- 接受new關鍵字指令後,檢查指令參數是否能在常量池中定位到一個類的符號引用
- 而後檢查符號引用對應的類是否已被加載、解析和初始化。若是沒有就執行。
- 類加載經過後,虛擬機爲新生的對象分配內存。
- 內存分配完成後,虛擬機須要將分配到的內存空間都初始化爲零值。
- 虛擬機設置對象頭信息。
內存分配的方式:
- 指正碰撞,內存是規整的,全部用過的內存放在一邊,空閒的內存放在另外一邊,中間放着一個指針做爲分界點的指示器。
- 空閒列表,內存不規整,虛擬機維護一個列表用來記錄那些內存是可用的,分配時從中找到足夠的內存劃分給對象,並更新表記錄。
保證線程安全的方式:
- CAS,對分配的內存空間進行同步處理,採用CAS配上失敗重試的方式保證操做的原子性。
- 線程分配緩衝區,把內存分配的動做按照線程劃分在不一樣的空間之中進行,即每一個線程在Java堆中預先分配好內存。
對象的內存佈局:
- 對象在內存中存儲的佈局能夠分爲三塊區域對象頭、實例數據和對齊填充。
- 對象頭,又能夠分爲兩部分,第一部分存儲自身運行時數據,第二部分是類型指針
- 自身運行數據主要包括哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳。
- 類型指針即對象指向他的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例。
- 實例數據,對象真正存儲的有用的有效信息,繼承父類的字段也會包含。
- 對齊填充,對象起始地址必須是8的整數倍,對齊填充起到了佔位符的做用。
對象的訪問定位:
- 使用句柄的訪問方式,堆中將劃分出來一塊內存做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址。reference中存儲的是穩定的句柄地址,若是對象被移動,reference的地址無需改變。
- 使用直接指針訪問,reference中存儲的直接就是對象的地址。直接指針訪問能夠減小指針定位的時間開銷。
三、OOM異常
- Java堆溢出,Java堆用於存儲對象實例,只要不斷的建立對象,而且保證GC Root到對象之間有可達路徑來避免垃圾回收,那麼對象數量到達最大堆的容量限制後就會產生內存溢出異常。
package com.ecut.exception; import java.util.ArrayList; import java.util.List; /** * -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError */ public class HeapOOM { static class OOMObject{ } public static void main(String[] args) { List<OOMObject> list = new ArrayList<>(); while (true){ list.add(new OOMObject()); } } }
運行結果以下:
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid6776.hprof ... Heap dump file created [28247587 bytes in 0.149 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at com.ecut.exception.HeapOOM.main(HeapOOM.java:17)
- 虛擬機棧和本地方法棧溢出,若是線程請求的棧深度大於虛擬機所容許的最大深度,將拋出StackOverflowError異常,若是虛擬機在拓展棧時沒法申請到足夠的內存空間則拋出OutOfMemoryError異常。
package com.ecut.exception; /** * -Xss128k */ public class JavaVMStackSOF { private int stackLength = 1; public void stackLeak(){ stackLength++; stackLeak(); } public static void main(String[] args) { JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF(); try { javaVMStackSOF.stackLeak(); }catch (Exception e){ System.out.println("stack length :" + javaVMStackSOF.stackLength); throw e; } } }
運行結果以下:
Exception in thread "main" java.lang.StackOverflowError at com.ecut.exception.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
建立線程致使內存溢出異常:
package com.ecut.exception; /** * -Xss2M */ 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 javaVMStackOOM = new JavaVMStackOOM(); javaVMStackOOM.stackLeakByThread(); } }
- 方法區和運行時常量池溢出
package com.ecut.exception; import java.util.ArrayList; import java.util.List; /** * -XX:PermSize=10M -XX:MaxPermSize=10M JDK 1.6 會拋出OOM異常,JDK1.7開始了去永久代 */ public class RuntimeConstantPoolOOM { public static void main(String[] args) { //使用list保持着產量池的引用,避免fullGC回收常量池的行爲 List<String> list = new ArrayList<>(); int i = 0 ; while(true){ list.add(String.valueOf(i++)); } } }
String.intern()方法若是字符串常量池中已經包含了一個等於String對象的字符串,則返回表明池中這個字符串的String對象。不然將此對象包含的字符串添加到常量池中。
package com.ecut.exception; public class RuntimeConstantPool { public static void main(String[] args) { /*jdk1.6 intern方法會把首次遇到的字符串實例複製到永久代中,返回的也是這個永久代中的這個字符串實例的引用。 StringBuilder建立的字符串實例在Java堆上,因此必然不是同一個引用 jdk1.7中intern實現只是在常量池中記錄首次出現的實例引用,所以intern返回的引用和StringBuilder建立的那個字符 串實例時同一個*/ String s1 = new StringBuilder("計算機").append("軟件").toString(); System.out.println(s1.intern() == s1); } }
運行結果:
true
- 本機直接內存溢出
package com.ecut.exception; import sun.misc.Unsafe; import java.lang.reflect.Field; /** * -Xmx20M -XX:MaxDirectMemorySize = 10M */ public class DirectMemoryOOM { private static final int _1MB = 1024*1024; public static void main(String[] args) throws IllegalAccessException { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true){ unsafe.allocateMemory(_1MB); } } }
運行結果以下:
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at com.ecut.exception.DirectMemoryOOM.main(DirectMemoryOOM.java:18)
源碼地址:
https://github.com/SaberZheng/jvm-test
轉載請於明顯處標明出處: