jvm記憶體分配及對象創建和回收過程

  • 2019 年 10 月 4 日
  • 筆記

版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。

本文鏈接:https://blog.csdn.net/qq_37933685/article/details/80617250

個人部落格:https://suveng.github.io/blog/​​​​​​​

Java歷史

2004.9 jdk1.5 tiger 自動裝箱拆箱,泛型,,註解,枚舉,變長參數,增強for循環 spring2.x spring4.x

2006 jdk1.6 javaee Javase Javame jdk6

  1. 提供腳本支援
  2. 提供編譯api以及http伺服器api

2009 jdk1.7 收購sun 74億

2014 jdk1.8

2017 jdk1.9

2018 jdk10

java 技術體系

Java程式設計語言

java 虛擬機

class 類文件格式

Java API

第三方Java類庫

Java8新特性

  1. 介面默認方法和靜態方法
  2. lambda表達式和函數式編程
  3. dateAPI
  4. 重複註解
  5. nashorn JavaScript引擎

jvm可視化監控工具

jconsole.exe

在jdk/bin目錄下,雙擊打開可運行,監控嗎某個Java程式的狀態

編寫測試類觀察jvm記憶體

JconsoleTest.java

package jconsole;    import java.util.ArrayList;  import java.util.List;    /**   * @author Veng Su [email protected]   * @date 2018/4/29 11:28   */  public class JconsoleTest {      public static void main(String[] args) throws InterruptedException {          Thread.sleep(5000);          fill(1000);      }        private static void fill(int n) throws InterruptedException {          List<JconsoleTest> jconsoleTests =new ArrayList<JconsoleTest>();          for (int i=0;i<n;i++){              Thread.sleep(200);              jconsoleTests.add(new JconsoleTest());          }      }  }

jvm記憶體溢出

Main.java

import java.util.ArrayList;  import java.util.List;    /**   * @author Veng Su [email protected]   * @date 2018/4/29 10:39   */  public class Main {      public static void main(String[] args) {  //        測試堆記憶體溢出          List<Demo> demoList=new ArrayList<Demo>();          while (true){              demoList.add(new Demo());          }      }  }

Demo.java

import java.util.ArrayList;  import java.util.List;    /**   * @author Veng Su [email protected]   * @date 2018/4/29 10:39   */  public class Main {      public static void main(String[] args) {  //        測試堆記憶體溢出          List<Demo> demoList=new ArrayList<Demo>();          while (true){              demoList.add(new Demo());          }      }  }

拋出的異常:

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:265)  	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)  	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)  	at java.util.ArrayList.add(ArrayList.java:462)  	at Main.main(Main.java:13)

jvm參數

導出堆記憶體

-XX:+HeapDumpOnOutOfMemoryError -Xms20m -Xmx20m

Java虛擬機的記憶體管理

運行時數據區

執行緒共享區

方法區

​ 運行時常量池

Java堆

執行緒獨立區

虛擬機棧

​ 存放方法運行時所需的數據,稱為棧幀

本地方法棧

​ 為jvm調用到的native,即本地方法服務

程式計數器

​ 記錄當前執行緒執行到位元組碼的行號

程式計數器

  1. 如果執行緒執行的是Java程式碼,這個計數器記錄的正在執行的虛擬機位元組碼指令的地址,如果正在執行的native方法,這個計數器的值為undefined
  2. 此區域是唯一一個在Java虛擬機規範中沒有規定任何的OutOfMemoryError的情況的區域

Java虛擬機棧

  1. 這個描述的是Java方法執行的動態記憶體模型
  2. 棧幀:每個方法執行都會創建一個棧幀,伴隨方法從創建到執行完成,用於存儲局部變數表,操作數棧,動態鏈接,方法出口等
  3. 局部變數表:存放編譯期已知的各種基本數據類型,引用類型,returnAddress類型 局部變數表的記憶體空間在編譯期完成分配,在進入一個方法時,這個方法需要在幀分配多少記憶體是固定的,在方法運行期間是不會改變的
  4. 虛擬機棧的大小 可能存在StackOverFlowError OutOfMemoryError記憶體不足,申請不到記憶體空間了

本地方法棧

Java虛擬機棧為虛擬機執行Java方法服務

本地方法棧為虛擬機執行native方法服務

Java堆

存放對象實例

垃圾搜集器管理的主要區域

新生代,老年代,Eden空間

申請不到空間同樣拋出outofmemoryerror

方法區

存儲虛擬機載入的類資訊,常量,靜態變數,及時編譯器編譯後的程式碼等數據

​ 類資訊:

​ 類的版本

​ 欄位

​ 方法

​ 介面

方法區和永久代 Hotspot使用永久代實現方法區,兩者不等價

垃圾回收在方法區的行為

異常的定義

申請空間失敗拋出outofmemoryerror

運行時常量池

常量池相當於一個hashset存放這寫常量,

而new 出來的實例肯定是放到堆記憶體中去

運行時常量和位元組碼常量的區別,運行時創建的常量和編譯期創建的常量的區別

直接記憶體

對象的創建

給對象分配記憶體的方法

  1. 指針碰撞
  2. 空閑列表

可能會出現執行緒安全性問題

如何解決

執行緒同步 缺點:效率低

本地分配緩衝

對象的結構

  1. header (對象頭) 自身運行時數據(MarkWord) ​ 哈希值 GC分代年齡 鎖狀態標誌 執行緒持有的鎖 偏向執行緒ID 偏向時間戳 類型指針、
  1. instanceData
  2. Padding 佔位符填充8的整數倍的作用

對象的訪問定位

  1. 使用句柄 定位句柄池,在找到對象地址
  2. 直接指針 直接找到對象地址 性能高 Hotspot使用直接地址定位

其他虛擬機

Sun hotshot

Bea JRockit

IBM J9

虛擬機的發展

  1. sun classic vm
  • 世界山第一個商用虛擬機
  • 只能使用純解釋器的方式執行java程式碼
  1. exact vm
  • exact memory management 準確試記憶體管理
  • 編譯器和解釋器混合工作以及兩級及時編譯器
  • 只在 Solaris平台發布
  1. hotspot vm
  2. kvm(kilobyte)
  3. JRocket BEA 世界上最快的虛擬機 專註伺服器端的應用 優勢 ​ 垃圾搜集器 ​ MissionControll服務套件 尋找運行時的記憶體泄露的問題
  4. J9 IBM Technology for Java virtual machine
  5. Azul vm
  6. Liquid vm
  7. Dalvik vm 不是Java虛擬機 暫存器架構,非棧架構 Google的
  8. Microsoft jvm 只能運行在windows平台下
  9. taobaovm 深度訂製

垃圾回收

如何判定對象為垃圾對象

  1. 引用計數法 在對象中添加一個引用計數器,當有地方引用這個對象的時候,引用計數器的值就加1,當引用失效的時候,計數器的值就減1 -verbose :gc -XX:+PrintGCDetails 列印垃圾回收的資訊
  2. 可達性分析法 GCRoot對象
    1. 虛擬機棧
    2. 方法區類屬性所引用的對象
    3. 方法區常量所引用的對象
    4. 本地方法棧所引用的對象

如何回收

  1. 回收策略
    1. 標記-清除演算法 效率問題 空間問題
    2. 複製演算法 堆
      1. 新生代
        1. Eden 伊甸園
        2. survivor 存活區
        3. Tenured Gen
      2. 老年代

      虛擬機棧 本地方法棧 程式計數器

    3. 標記-整理-清除演算法 針對老年代
    4. 分代收集演算法
  2. 垃圾回收器
    1. Serial 單執行緒
    2. Parnew
    3. parallel scanvenge收集器 -XX:MaxGFPauseMillis 垃圾收集器停頓時間1 -XX:CGTimeRatio 吞吐量大小 複製演算法(新生代收集器) 多執行緒收集器 達到可控吞吐量 吞吐量:CPU運行程式碼的時間與CPU消耗的總時間的比值
    4. CMS收集器 current Mark sweep
      1. 工作過程
        1. 初始標記
        2. 並發標記
        3. 重新標記
        4. 並發清理
      2. 優點
        1. 並發收集
        2. 低停頓
      3. 缺點
        1. 佔用大量CPU資源
        2. 無法處理浮動垃圾
        3. 出現current mode failure
        4. 空間碎片
    5. G1

記憶體分配

只要電腦運行記憶體大於2g,CPU核心是多核, 默認是ServerVM

可以看到我們的虛擬機是HotSpot

記憶體分配策略

  1. 優先分配到新生代的Eden區 VM option -verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8
  2. 大對象直接進入到老年代 指定進入老年代的對象的記憶體大小 -XX:PretenureSizeThreshold
  3. 長期存活的對象分配到老年代 -XX:MaxTenuringThreshold 具有年齡計數器。每次回收時存活,年齡加1.到達閾值就進入老年代中
  4. 空間分配擔保 如果記憶體空間不足,向擔保借; -XX:(+/-)HandlePromotionFailure
  5. 逃逸分析和棧上分配 通過逃逸分析,分析出沒有逃逸的對象,直接在棧上分配空間。 什麼是逃逸分析? 分析對象的作用域。如果對象只有在方法體內有效,則判定為沒有逃逸。否則,為逃逸對象
  6. 動態對象年齡判斷

虛擬機工具

  • JPS JAVA PROCESS STATUS JPS 名稱: jps – Java Virtual Machine Process Status Tool 命令用法: jps options hostid ​ options:命令選項,用來對輸出格式進行控制 ​ hostid:指定特定主機,可以是ip地址和域名, 也可以指定具體協議,埠。 功能描述: jps是用於查看有權訪問的hotspot虛擬機的進程. 當未指定hostid時,默認查看本機jvm進程,否者查看指定的hostid機器上的jvm進程,此時hostid所指機器必須開啟jstatd服務。 jps可以列出jvm進程lvmid,主類類名,main函數參數, jvm參數,jar名稱等資訊。
    1. 沒添加option的時候,默認列出VM標示符號和簡單的class或jar名稱
    2. -p :僅僅顯示VM 標示,不顯示jar,class, main參數等資訊.
    3. -m:輸出主函數傳入的參數. 下的hello 就是在執行程式時從命令行輸入的參數
    4. -l: 輸出應用程式主類完整package名稱或jar完整名稱.
    5. -v: 列出jvm參數, -Xms20m -Xmx50m是啟動程式指定的jvm參數
    6. -V: 輸出通過.hotsportrc或-XX:Flags=指定的jvm參數
    7. -Joption:傳遞參數到javac 調用的java lancher.
  • JSTAT jstat命令可以類裝載,記憶體,垃圾收集,jit編譯。命令的格式如下: jstat [-命令選項] [vmid] [間隔時間/毫秒] [查詢次數]
    1. 類載入統計 jstat -class Loaded:載入class的數量 Bytes:所佔用空間大小 Unloaded:未載入數量 Bytes:未載入佔用空間 Time:時間
    2. 編譯統計 jstat -compiler Compiled:編譯數量。 Failed:失敗數量 Invalid:不可用數量 Time:時間 FailedType:失敗類型 FailedMethod:失敗的方法
    3. 垃圾回收統計 jstat -gccapacity S0C:第一個倖存區的大小 S1C:第二個倖存區的大小 S0U:第一個倖存區的使用大小 S1U:第二個倖存區的使用大小 EC:伊甸園區的大小 EU:伊甸園區的使用大小 OC:老年代大小 OU:老年代使用大小 MC:方法區大小 MU:方法區使用大小 CCSC:壓縮類空間大小 CCSU:壓縮類空間使用大小 YGC:年輕代垃圾回收次數 YGCT:年輕代垃圾回收消耗時間 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間
    4. 新生代垃圾回收統計 jstat -gcnew S0C:第一個倖存區大小 S1C:第二個倖存區的大小 S0U:第一個倖存區的使用大小 S1U:第二個倖存區的使用大小 TT:對象在新生代存活的次數 MTT:對象在新生代存活的最大次數 DSS:期望的倖存區大小 EC:伊甸園區的大小 EU:伊甸園區的使用大小 YGC:年輕代垃圾回收次數 YGCT:年輕代垃圾回收消耗時間
    5. 新生代記憶體統計 jstat -gcnewcapacity NGCMN:新生代最小容量 NGCMX:新生代最大容量 NGC:當前新生代容量 S0CMX:最大倖存1區大小 S0C:當前倖存1區大小 S1CMX:最大倖存2區大小 S1C:當前倖存2區大小 ECMX:最大伊甸園區大小 EC:當前伊甸園區大小 YGC:年輕代垃圾回收次數 FGC:老年代回收次數
    6. 老年代垃圾回收統計 jstat -gcold MC:方法區大小 MU:方法區使用大小 CCSC:壓縮類空間大小 CCSU:壓縮類空間使用大小 OC:老年代大小 OU:老年代使用大小 YGC:年輕代垃圾回收次數 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間
    7. 老年代記憶體統計 jstat -gcoldcapacity OGCMN:老年代最小容量 OGCMX:老年代最大容量 OGC:當前老年代大小 OC:老年代大小 YGC:年輕代垃圾回收次數 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間
    8. 元數據空間統計 jstat -gcmetacapacity MCMN:最小元數據容量 MCMX:最大元數據容量 MC:當前元數據空間大小 CCSMN:最小壓縮類空間大小 CCSMX:最大壓縮類空間大小 CCSC:當前壓縮類空間大小 YGC:年輕代垃圾回收次數 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間
    9. 總結垃圾回收統計 jstat -gcutil S0:倖存1區當前使用比例 S1:倖存2區當前使用比例 E:伊甸園區使用比例 O:老年代使用比例 M:元數據區使用比例 CCS:壓縮使用比例 YGC:年輕代垃圾回收次數 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間
    10. JVM編譯方法統計 jstat -printcompilation Compiled:最近編譯方法的數量 Size:最近編譯方法的位元組碼數量 Type:最近編譯方法的編譯類型。 Method:方法名標識。
  • JINFO jinfo是jdk自帶的命令,用來查看jvm的配置參數。通常會先使用jps查看java進程的id,然後使用jinfo查看指定pid的jvm資訊 查看jvm的參數 jinfo -flags process_id 查看java系統參數 jinfo -sysprops process_id
  • JMAP JVM Memory Map命令用於生成heap dump文件,如果不使用這個命令,還可以使用-XX:+HeapDumpOnOutOfMemoryError參數來讓虛擬機出現OOM的時候自動生成dump文件。 參數 option:選項參數,不可同時使用多個選項參數 pid:java進程id,命令ps -ef | grep java獲取 executable:產生核心dump的java可執行文件 core:需要列印配置資訊的核心文件 remote-hostname-or-ip:遠程調試的主機名或ip server-id:可選的唯一id,如果相同的遠程主機上運行了多台調試伺服器,用此選項參數標識伺服器 options參數 heap : 顯示Java堆詳細資訊 histo : 顯示堆中對象的統計資訊 permstat :Java堆記憶體的永久保存區域的類載入器的統計資訊 finalizerinfo : 顯示在F-Queue隊列等待Finalizer執行緒執行finalizer方法的對象 dump : 生成堆轉儲快照 F : 當-dump沒有響應時,強制生成dump快照 -dump dump堆到文件,format指定輸出格式,live指明是活著的對象,file指定文件名 -heap 列印heap的概要資訊,GC使用的演算法,heap的配置及使用情況,可以用此來判斷記憶體目前的使用情況以及垃圾回收情況 -finalizerinfo 列印等待回收的對象資訊, -histo 列印堆的對象統計,包括對象數、記憶體大小等等。jmap -histo:live 這個命令執行,JVM會先觸發gc,然後再統計資訊 jmap -histo:live 24971 | grep com.yuhuo 查詢類名包含com.yuhuo的資訊 jmap -histo:live 24971 | grep com.yuhuo > histo.txt 保存資訊到histo.txt文件 -permstat 列印Java堆記憶體的永久區的類載入器的智慧統計資訊。對於每個類載入器而言,它的名稱、活躍度、地址、父類載入器、它所載入的類的數量和大小都會被列印。此外,包含的字元串數量和大小也會被列印。 -F 強制模式。如果指定的pid沒有響應,請使用jmap -dump或jmap -histo選項。此模式下,不支援live子選項。
  • JHAT JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML伺服器,生成dump的分析結果後,可以在瀏覽器中查看。在此要注意,一般不會直接在伺服器上進行分析,因為jhat是一個耗時並且耗費硬體資源的過程,一般把伺服器生成的dump文件複製到本地或其他機器上進行分析。【記憶體分析】 參數 -J< flag > 因為 jhat 命令實際上會啟動一個JVM來執行, 通過 -J 可以在啟動JVM時傳入一些啟動參數. 例如, -J-Xmx512m 則指定運行 jhat 的Java虛擬機使用的最大堆記憶體為 512 MB. 如果需要使用多個JVM啟動參數,則傳入多個 -Jxxxxxx. -stack false|true 關閉對象分配調用棧跟蹤(tracking object allocation call stack)。 如果分配位置資訊在堆轉儲中不可用. 則必須將此標誌設置為 false. 默認值為 true. -refs false|true 關閉對象引用跟蹤(tracking of references to objects)。 默認值為 true. 默認情況下, 返回的指針是指向其他特定對象的對象,如反向鏈接或輸入引用(referrers or incoming references), 會統計/計算堆中的所有對象。 -port port-number 設置 jhat HTTP server 的埠號. 默認值 7000。 -exclude exclude-file 指定對象查詢時需要排除的數據成員列表文件(a file that lists data members that should be excluded from the reachable objects query)。 例如, 如果文件列列出了 java.lang.String.value , 那麼當從某個特定對象 Object o 計算可達的對象列表時, 引用路徑涉及 java.lang.String.value 的都會被排除。 -baseline exclude-file 指定一個基準堆轉儲(baseline heap dump)。 在兩個 heap dumps 中有相同 object ID 的對象會被標記為不是新的(marked as not being new). 其他對象被標記為新的(new). 在比較兩個不同的堆轉儲時很有用。 -debug int 設置 debug 級別. 0 表示不輸出調試資訊。 值越大則表示輸出更詳細的 debug 資訊。 -version 啟動後只顯示版本資訊就退出。
  • JSTACK
  • JCONSOLE

性能調優

  1. 常用思路
    1. 優化sql
    2. 監控CPU
    3. 監控記憶體
      • FULL GC 垃圾收集時間過長
        • 解決方案
          • 調整堆記憶體大小

問題:

  1. 不定期出現記憶體溢出,把堆記憶體加大也沒用,導出記憶體資訊沒有任何資訊.記憶體監控,也正常

處理思路:

  1. 控制變數法
    1. 硬體環境
      1. CPU
      2. 記憶體
    2. 軟體環境
      1. 作業系統
      2. Java版本
      3. 容器
      4. 程式碼問題