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
- 提供腳本支援
- 提供編譯api以及http伺服器api
2009 jdk1.7 收購sun 74億
2014 jdk1.8
2017 jdk1.9
2018 jdk10
java 技術體系
Java程式設計語言
java 虛擬機
class 類文件格式
Java API
第三方Java類庫
Java8新特性
- 介面默認方法和靜態方法
- lambda表達式和函數式編程
- dateAPI
- 重複註解
- 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,即本地方法服務
程式計數器
記錄當前執行緒執行到位元組碼的行號
程式計數器
- 如果執行緒執行的是Java程式碼,這個計數器記錄的正在執行的虛擬機位元組碼指令的地址,如果正在執行的native方法,這個計數器的值為undefined
- 此區域是唯一一個在Java虛擬機規範中沒有規定任何的OutOfMemoryError的情況的區域
Java虛擬機棧
- 這個描述的是Java方法執行的動態記憶體模型
- 棧幀:每個方法執行都會創建一個棧幀,伴隨方法從創建到執行完成,用於存儲局部變數表,操作數棧,動態鏈接,方法出口等
- 局部變數表:存放編譯期已知的各種基本數據類型,引用類型,returnAddress類型 局部變數表的記憶體空間在編譯期完成分配,在進入一個方法時,這個方法需要在幀分配多少記憶體是固定的,在方法運行期間是不會改變的
- 虛擬機棧的大小 可能存在StackOverFlowError OutOfMemoryError記憶體不足,申請不到記憶體空間了
本地方法棧
Java虛擬機棧為虛擬機執行Java方法服務
本地方法棧為虛擬機執行native方法服務
Java堆
存放對象實例
垃圾搜集器管理的主要區域
新生代,老年代,Eden空間
申請不到空間同樣拋出outofmemoryerror
方法區
存儲虛擬機載入的類資訊,常量,靜態變數,及時編譯器編譯後的程式碼等數據
類資訊:
類的版本
欄位
方法
介面
方法區和永久代 Hotspot使用永久代實現方法區,兩者不等價
垃圾回收在方法區的行為
異常的定義
申請空間失敗拋出outofmemoryerror
運行時常量池
常量池相當於一個hashset存放這寫常量,
而new 出來的實例肯定是放到堆記憶體中去
運行時常量和位元組碼常量的區別,運行時創建的常量和編譯期創建的常量的區別
直接記憶體
對象的創建

給對象分配記憶體的方法
- 指針碰撞
- 空閑列表
可能會出現執行緒安全性問題
如何解決
執行緒同步 缺點:效率低
本地分配緩衝
對象的結構
- header (對象頭) 自身運行時數據(MarkWord) 哈希值 GC分代年齡 鎖狀態標誌 執行緒持有的鎖 偏向執行緒ID 偏向時間戳 類型指針、

- instanceData
- Padding 佔位符填充8的整數倍的作用
對象的訪問定位
- 使用句柄 定位句柄池,在找到對象地址
- 直接指針 直接找到對象地址 性能高 Hotspot使用直接地址定位
其他虛擬機
Sun hotshot
Bea JRockit
IBM J9
虛擬機的發展
- sun classic vm
- 世界山第一個商用虛擬機
- 只能使用純解釋器的方式執行java程式碼
- exact vm
- exact memory management 準確試記憶體管理
- 編譯器和解釋器混合工作以及兩級及時編譯器
- 只在 Solaris平台發布
- hotspot vm
- kvm(kilobyte)
- JRocket BEA 世界上最快的虛擬機 專註伺服器端的應用 優勢 垃圾搜集器 MissionControll服務套件 尋找運行時的記憶體泄露的問題
- J9 IBM Technology for Java virtual machine
- Azul vm
- Liquid vm
- Dalvik vm 不是Java虛擬機 暫存器架構,非棧架構 Google的
- Microsoft jvm 只能運行在windows平台下
- taobaovm 深度訂製
垃圾回收
如何判定對象為垃圾對象
- 引用計數法 在對象中添加一個引用計數器,當有地方引用這個對象的時候,引用計數器的值就加1,當引用失效的時候,計數器的值就減1 -verbose :gc -XX:+PrintGCDetails 列印垃圾回收的資訊
- 可達性分析法 GCRoot對象
- 虛擬機棧
- 方法區類屬性所引用的對象
- 方法區常量所引用的對象
- 本地方法棧所引用的對象
如何回收
- 回收策略
- 標記-清除演算法 效率問題 空間問題
- 複製演算法 堆
- 新生代
- Eden 伊甸園
- survivor 存活區
- Tenured Gen
- 老年代
虛擬機棧 本地方法棧 程式計數器
- 新生代
- 標記-整理-清除演算法 針對老年代
- 分代收集演算法
- 垃圾回收器
- Serial 單執行緒
- Parnew
- parallel scanvenge收集器 -XX:MaxGFPauseMillis 垃圾收集器停頓時間1 -XX:CGTimeRatio 吞吐量大小 複製演算法(新生代收集器) 多執行緒收集器 達到可控吞吐量 吞吐量:CPU運行程式碼的時間與CPU消耗的總時間的比值
- CMS收集器 current Mark sweep
- 工作過程
- 初始標記
- 並發標記
- 重新標記
- 並發清理
- 優點
- 並發收集
- 低停頓
- 缺點
- 佔用大量CPU資源
- 無法處理浮動垃圾
- 出現current mode failure
- 空間碎片
- 工作過程
- G1
記憶體分配

只要電腦運行記憶體大於2g,CPU核心是多核, 默認是ServerVM
可以看到我們的虛擬機是HotSpot
記憶體分配策略
- 優先分配到新生代的Eden區 VM option -verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC -Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8
- 大對象直接進入到老年代 指定進入老年代的對象的記憶體大小 -XX:PretenureSizeThreshold
- 長期存活的對象分配到老年代 -XX:MaxTenuringThreshold 具有年齡計數器。每次回收時存活,年齡加1.到達閾值就進入老年代中
- 空間分配擔保 如果記憶體空間不足,向擔保借; -XX:(+/-)HandlePromotionFailure
- 逃逸分析和棧上分配 通過逃逸分析,分析出沒有逃逸的對象,直接在棧上分配空間。 什麼是逃逸分析? 分析對象的作用域。如果對象只有在方法體內有效,則判定為沒有逃逸。否則,為逃逸對象
- 動態對象年齡判斷
虛擬機工具
- 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名稱等資訊。
- 沒添加option的時候,默認列出VM標示符號和簡單的class或jar名稱
- -p :僅僅顯示VM 標示,不顯示jar,class, main參數等資訊.
- -m:輸出主函數傳入的參數. 下的hello 就是在執行程式時從命令行輸入的參數
- -l: 輸出應用程式主類完整package名稱或jar完整名稱.
- -v: 列出jvm參數, -Xms20m -Xmx50m是啟動程式指定的jvm參數
- -V: 輸出通過.hotsportrc或-XX:Flags=指定的jvm參數
- -Joption:傳遞參數到javac 調用的java lancher.
- JSTAT jstat命令可以類裝載,記憶體,垃圾收集,jit編譯。命令的格式如下: jstat [-命令選項] [vmid] [間隔時間/毫秒] [查詢次數]
- 類載入統計
jstat -class
Loaded:載入class的數量 Bytes:所佔用空間大小 Unloaded:未載入數量 Bytes:未載入佔用空間 Time:時間 - 編譯統計
jstat -compiler
Compiled:編譯數量。 Failed:失敗數量 Invalid:不可用數量 Time:時間 FailedType:失敗類型 FailedMethod:失敗的方法 - 垃圾回收統計
jstat -gccapacity
S0C:第一個倖存區的大小 S1C:第二個倖存區的大小 S0U:第一個倖存區的使用大小 S1U:第二個倖存區的使用大小 EC:伊甸園區的大小 EU:伊甸園區的使用大小 OC:老年代大小 OU:老年代使用大小 MC:方法區大小 MU:方法區使用大小 CCSC:壓縮類空間大小 CCSU:壓縮類空間使用大小 YGC:年輕代垃圾回收次數 YGCT:年輕代垃圾回收消耗時間 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間 - 新生代垃圾回收統計
jstat -gcnew
S0C:第一個倖存區大小 S1C:第二個倖存區的大小 S0U:第一個倖存區的使用大小 S1U:第二個倖存區的使用大小 TT:對象在新生代存活的次數 MTT:對象在新生代存活的最大次數 DSS:期望的倖存區大小 EC:伊甸園區的大小 EU:伊甸園區的使用大小 YGC:年輕代垃圾回收次數 YGCT:年輕代垃圾回收消耗時間 - 新生代記憶體統計
jstat -gcnewcapacity
NGCMN:新生代最小容量 NGCMX:新生代最大容量 NGC:當前新生代容量 S0CMX:最大倖存1區大小 S0C:當前倖存1區大小 S1CMX:最大倖存2區大小 S1C:當前倖存2區大小 ECMX:最大伊甸園區大小 EC:當前伊甸園區大小 YGC:年輕代垃圾回收次數 FGC:老年代回收次數 - 老年代垃圾回收統計
jstat -gcold
MC:方法區大小 MU:方法區使用大小 CCSC:壓縮類空間大小 CCSU:壓縮類空間使用大小 OC:老年代大小 OU:老年代使用大小 YGC:年輕代垃圾回收次數 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間 - 老年代記憶體統計
jstat -gcoldcapacity
OGCMN:老年代最小容量 OGCMX:老年代最大容量 OGC:當前老年代大小 OC:老年代大小 YGC:年輕代垃圾回收次數 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間 - 元數據空間統計
jstat -gcmetacapacity
MCMN:最小元數據容量 MCMX:最大元數據容量 MC:當前元數據空間大小 CCSMN:最小壓縮類空間大小 CCSMX:最大壓縮類空間大小 CCSC:當前壓縮類空間大小 YGC:年輕代垃圾回收次數 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間 - 總結垃圾回收統計
jstat -gcutil
S0:倖存1區當前使用比例 S1:倖存2區當前使用比例 E:伊甸園區使用比例 O:老年代使用比例 M:元數據區使用比例 CCS:壓縮使用比例 YGC:年輕代垃圾回收次數 FGC:老年代垃圾回收次數 FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間 - 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
性能調優
- 常用思路
- 優化sql
- 監控CPU
- 監控記憶體
- FULL GC 垃圾收集時間過長
- 解決方案
- 調整堆記憶體大小
- 解決方案
- FULL GC 垃圾收集時間過長
問題:
- 不定期出現記憶體溢出,把堆記憶體加大也沒用,導出記憶體資訊沒有任何資訊.記憶體監控,也正常
處理思路:
- 控制變數法
- 硬體環境
- CPU
- 記憶體
- 軟體環境
- 作業系統
- Java版本
- 容器
- 程式碼問題
- 硬體環境