《深刻理解Java虛擬機》讀書筆記一

第二章 Java內存區域與內存溢出異常html

一、運行時數據區域java

程序計數器:git

  • 當前線程所執行的字節碼的行號指示器,用於存放下一條須要運行的指令。
  • 運行速度最快位於處理器內部。
  • 線程私有。

虛擬機棧:github

  • 描述的是Java方法執行的內存模型,用於存放對象的引用和基本數據類型。
  • 每一個方法執行的時候都會建立一個棧幀(stack frame)用於存放 局部變量表、操做棧、動態連接、方法出口。
  • 線程私有,生命週期與線程相同。

方法棧:安全

  • 和虛擬機棧功能相似,管理本地的方法調用。
  • 虛擬機棧爲虛擬機執行的Java方法的方法服務,方法棧則爲虛擬機使用的本地方法的服務。

堆:app

  • 虛擬機最大的一塊區域,虛擬機啓動的時候建立。
  • 用於存放對象的實例,全部對象實例和數據的在堆上分配內存。
  • 線程共享的區域。

方法區:jvm

  • 用於存放一些類信息,常量,靜態變量和即時編譯後的代碼等數據。
  • 線程共享的區域。

運行時常量池:ide

  • 方法區的一部分,用於存放編譯期生成的各類字面量和符號引用。

直接內存:佈局

  • 不屬於運行時數據區,堆外內存。

二、HotSpot虛擬機對象探祕ui

對象的建立:

  • 接受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

轉載請於明顯處標明出處:

https://www.cnblogs.com/AmyZheng/p/10504443.html

相關文章
相關標籤/搜索