【JVM 知識體系框架總結(jié)】

JVM 內(nèi)存分布

  • 線程共享數(shù)據(jù)區(qū):
    方法區(qū)->類信息,靜態(tài)變量
    堆->數(shù)組對象
  • 線程隔離區(qū)
    虛擬機棧-> 方法
    本地方法棧->本地方法庫 native
  • 堆、程序計數(shù)器
  • JVM 運行數(shù)據(jù)


程序計數(shù)器

線程隔離 ,比較小的內(nèi)存空間,當(dāng)前線程所執(zhí)行的字節(jié)碼的行號
線程是一個獨立的執(zhí)行單元,由 CPU執(zhí)行
唯一沒有 OOM 的地方,由虛擬機維護(hù),所以不會出現(xiàn) OOM

虛擬機棧

執(zhí)行的是Java方法

方法的調(diào)用就是棧幀入虛擬機棧的過程
棧幀:局部變量表(變量) 、操作數(shù)棧(存放a+b的結(jié)果 )、 動態(tài)鏈接(對對象引用的地址),方法出口(return的值)
線程請求的棧深度大于虛擬機所允許的深度StackOverflowError

本地方法棧

執(zhí)行的是 native 方法的一塊 java內(nèi)存區(qū)域,一樣有 棧幀
hotspot將 Java 虛擬機棧和本地方法棧合二為一
jvm標(biāo)準(zhǔn)是 java 虛擬機棧和本地方法棧分開

java內(nèi)存中存放對象實例的區(qū)域,幾乎所有的對象實例都在這里分配
所有線程共享
新生代、老年代
jmap -heap pid;

方法區(qū)

各個線程共享的內(nèi)存區(qū)域
存儲已被虛擬機加載的類的信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)
Hotspot用永久代實現(xiàn)方法區(qū)(讓垃圾回收器可以管理方法區(qū)),對常量池的回收和卸載
方法區(qū)會拋出 OOM,當(dāng)他無法滿足內(nèi)存分配需求時

運行時常量池

運行時常量池是方法區(qū)的一部分,Class 中除了字段、方法、接口的 常量池,存放編譯器生成的字面量和符號引用,這部分內(nèi)容由類加載后進(jìn)入方法區(qū)的運行時常量池中存放。

StringTable是HashSet結(jié)構(gòu)
方法區(qū)的一部分,受到方法區(qū)的限制,依然會 OOM

Java 對象創(chuàng)建過程


<init> -> static方法 static代碼塊

  1. new 指令,判斷在常量池中有沒符號引用,有則已被加載過
  2. 判斷類是否被加載、解析、初始化
  3. 為新生對象在java堆里分配內(nèi)存空間
  1. 指針碰撞(內(nèi)存比較整齊)



    步驟:1. 分配內(nèi)存 2. 移動指針,非原子步驟可能出現(xiàn)并發(fā)問題,Java虛擬機采用 CAS 配上失敗重試的方式保證更新操作的原子性
    2)空閑列表(內(nèi)存比較亂)
    存儲堆內(nèi)存空閑地址
    步驟:1.分配內(nèi)存 2. 修改空閑列表地址 非原子步驟可能出現(xiàn)并發(fā)問題,Java虛擬機采用 CAS 配上失敗重試的方式保證更新操作的原子性

  1. 將分配到內(nèi)存空間都初始化零值
  2. 設(shè)置對象頭相關(guān)信息 (GC分代年齡、對象的HashCode、元數(shù)據(jù)信息)
  3. 執(zhí)行<init>方法

Java 對象內(nèi)存布局

對象屬性的值->實例數(shù)據(jù)
對象頭 64 位機器存 64 位,32 位機器存 32 位,8 的倍數(shù)


Java 對象的訪問

  1. 直接指針訪問


  2. 句柄訪問



    對比:

  3. 訪問效率:直接指針訪問效率高(hotspot采用這種方式)
  4. 垃圾回收:句柄訪問效率高,垃圾回收只用更新句柄池,而直接指針訪問方式則要更新 reference地址

垃圾回收算法

  1. 引用計數(shù)器



    當(dāng)對象實例分配給一個變量時,該變量計數(shù)設(shè)置為 1,當(dāng)任何其他變量被賦值為這個對象的引用的時,計數(shù)+1 (a =b,則b的引用對象實例計數(shù)器+1),當(dāng)一個對象實例的某個引用超過了生命周期(方法執(zhí)行完)或者被設(shè)置為一個新值,則該對象的實例引用計數(shù)器 -1
    無法解決循環(huán)引用
    可達(dá)性分析
    GC Root (虛擬機棧中的引用的對象、本地方法棧中引用的對象、方法區(qū)靜態(tài)屬性引用的對象、方法區(qū)常量引用的對象)

  2. 標(biāo)記-清除



    標(biāo)記需要回收的對象,在標(biāo)記完成后統(tǒng)一回收
    不足:
    1.效率問題,標(biāo)記清除 2 個過程效率都不高
    2.空間問題,標(biāo)記清除后產(chǎn)生大量不連續(xù)的內(nèi)存碎片,碎片過多當(dāng)程序需要分配較大的對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)一次垃圾回收動作

  3. 標(biāo)記-復(fù)制



    內(nèi)存塊 A存活的對象復(fù)制到內(nèi)存塊 B (Survivor to)里,然后將內(nèi)存塊A (Eden + Survivor from)清空,
    只有少部分對象移動,更多的對象是要被回收的
    Eden:Survivor from:Survivor to=8:1:1
    98%對象“朝生夕亡”,新生代可用內(nèi)存容量 90%(80%+10%),98%的對象可回收是一般情況,當(dāng)小于 90%的對象被回收的時候(10%以上的對象存活時),則 Survivor to 空間不夠,則需要依賴?yán)夏甏M(jìn)行分配擔(dān)保

  4. 標(biāo)記-整理



    老年代不適合復(fù)制算法

  5. 復(fù)制操作增多 2. 額外 50%空間浪費 3. 經(jīng)常需要額外的空間分配擔(dān)保 4.可能老年代中對象 100% 存活

步驟:

  1. 標(biāo)記
  2. 整理 將存活的對象移動到一端(左上方),從不規(guī)整變成規(guī)整,然后直接清理掉邊界以外的內(nèi)存

Serial 收集器


單線程垃圾回收器,用戶線程到安全點先暫定,然后 GC 線程單線程串行進(jìn)行,等 GC 線程回收完,然后用戶線程再繼續(xù)
特點:Stop the world
場景:桌面應(yīng)用 (gc時間短)
用于新生代,client 端

ParNew 收集器

Serial收集器的多線程版本



用于新生代,唯一能和CMS 收集器配合工作,運行在 server 模式下
-XX:ParallelGCThreads 限制垃圾收集器線程數(shù) = CPU 核數(shù)(過多會導(dǎo)致上下文切換消耗)
并行:多條垃圾收集線程并行工作,用戶線程仍然處于等待狀態(tài)
并發(fā):用戶線程與垃圾收集器同時執(zhí)行,用戶線程和垃圾線程在不同 CPU 上執(zhí)行

Parallel Scavenge 收集器

新生代收集器,復(fù)制算法,并行的多線程收集器
關(guān)注吞吐量優(yōu)先的收集器(吞吐量 = CPU 運行用戶代碼執(zhí)行時間/CPU 執(zhí)行總時間 ,比如: 99%時間執(zhí)行用戶線程,1%時間回收垃圾,這時吞吐量為 99%)高吞吐量可以高效率利用 CPU 時間,盡快完成程序的運算任務(wù),適合在后臺運算而不需要太多的交互任務(wù)
CMS 關(guān)注縮短垃圾回收停頓時間,適合與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗
-XX:MaxGCPauseMillis 參數(shù) GC 停頓時間,參數(shù)過小會頻繁 GC
-XX:GCTimeRatio 參數(shù),默認(rèn) 99%(用戶線程時間占 CPU 總時間的 99%)

Serial Old 收集器

是Serial 收集器的老年代版本
單線程老年代收集器,采用“標(biāo)記-整理”算法

Parallel Old 收集器

是 Parallel Scavenge收集器的老年代版本
多線程老年代收集器,采用“標(biāo)記-整理”算法

CMS 收集器

獲取最短回收停頓時間為目標(biāo)的收集器,采用“標(biāo)記-清除”算法,用于互聯(lián)網(wǎng)、B/S 系統(tǒng)重視響應(yīng)的系統(tǒng)


步驟:

  1. 初始標(biāo)記(不和用戶線程一起運行,耗時短)—— 標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對象,速度很快
  2. 并發(fā)標(biāo)記(和用戶線程一起運行,耗時長) —— 并發(fā)標(biāo)記階段就是進(jìn)行 GC RootsTracing,尋找 GC 引用鏈
  3. 重新標(biāo)記(不和用戶線程一起運行,耗時短)—— 為了修正并發(fā)標(biāo)記期間因用戶線程導(dǎo)致標(biāo)記產(chǎn)生變動的標(biāo)記記錄
  4. 并發(fā)清除(和用戶線程一起運行,耗時長)—— 掃描整個內(nèi)存區(qū)域

缺點 :

  1. 對 CPU 資源非常敏感(并發(fā)標(biāo)記階段時間長,占用用戶線程 CPU 時間)
  2. 無法處理浮動垃圾(程序在進(jìn)行并發(fā)清除時,用戶線程所產(chǎn)生的新垃圾)
  3. 標(biāo)記-清除產(chǎn)生空間碎片

G1 收集器

面向服務(wù)端應(yīng)用的垃圾收集器


Region->Remembered Set (解決 循環(huán)引用 )
檢查 Reference (程序?qū)eference類型寫操作,檢查 reference 引用類型)
步驟:

  1. 初始標(biāo)記 —— 標(biāo)記 GC Roots 能直接關(guān)聯(lián)到的對象
  2. 并發(fā)標(biāo)記 —— 從 GC Root 開始對堆中對象進(jìn)行可達(dá)性分析,找出存活對象 ,這一階段耗時較長,但可與用戶程序并發(fā)執(zhí)行
  3. 最終標(biāo)記(Remembered Set Logs->Remembered Set)—— 修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分標(biāo)記記錄,虛擬機將這段時間對象變化記錄在線程 Remembered Set Logs里面,最終標(biāo)記階段需要把 Remembered Set Logs的數(shù)據(jù)合并到 Remembered Set中
  4. 篩選回收(Live Data Counting and Evacuation)—— 只需要掃描 Remembered Set
    優(yōu)勢:
  5. 基于“標(biāo)記-整理” 為主和 Region 之間采用復(fù)制算法實現(xiàn)
  6. 可預(yù)測停頓,降低停頓時間,但G1 除了追求低停頓外,還能建立可預(yù)測的停頓時間模型
  7. G1 直接對 Java 堆中的 Region 進(jìn)行回收(新生代、老年代不再物理隔離,他們都是一部分 Region)
  8. 可預(yù)測的停頓時間模型,G1 跟蹤各個 Regions 里面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經(jīng)驗值),在后臺維護(hù)一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的 Region

堆內(nèi)存分配

Java 堆分布圖


對象分配的規(guī)則:

  1. 對象主要分配在新生代的 Eden 區(qū) ( Eden區(qū),F(xiàn)rom 區(qū)存活對象復(fù)制到 To區(qū),Eden區(qū),F(xiàn)rom區(qū)被回收,然后 To區(qū)對象拷貝到From區(qū),再進(jìn)行下一次垃圾回收 )
  2. 如果啟動了本地線程分配緩沖,將按線程優(yōu)先在 TLAB 上分配
  3. 少數(shù)情況下也可能直接分配到老年代 (放不下From和To區(qū)的都直接放到老年代)

大對象分配

大對象是指需要大量連續(xù)內(nèi)存空間的 Java 對象,最典型的大對象是是那種很長的字符串以及數(shù)組
-XX:PretenureSizeThreshold 設(shè)置大于該值的對象直接分配在老年代,避免在 Eden 區(qū)以及 2 個Survivior區(qū)之間發(fā)生大量的內(nèi)存復(fù)制

逃逸分析和棧上分配

逃逸分析:分析對象動態(tài)作用域,當(dāng)一個對象在方法中被定義后,它可能被外部方法所引用,稱為方法逃逸。甚至還有可能被外部線程訪問到,比如賦值給類變量或其他線程中訪問的實例變量,稱為線程逃逸。
棧上分配:把方法中的變量和對象直接分配到棧上,方法執(zhí)行完后自動銷毀,不需要垃圾回收介入,從而提高系統(tǒng)性能
-XX:+DoEscapeAnalysis 開啟逃逸分析(jdk1.8默認(rèn)開啟 )
-XX:-DoEscapeAnalysis 關(guān)閉逃逸分析

命令

  1. ps -ef | grep java
  2. jps -m(啟動參數(shù)) -l(類名) -v (JVM 參數(shù))
  3. jstat -gc 27660 250 20 監(jiān)視虛擬機各種運行狀態(tài)信息


  4. jinfo 27660 查看和調(diào)整進(jìn)程虛擬機(未被顯示指定的)參數(shù)信息
  5. jmap 生成堆轉(zhuǎn)儲快照 -XX:+HeapDumpOnOutOfMemoryError
    jmap -heap 9366;
    jmap -histo 9366 | more; 顯示堆中對象統(tǒng)計
    jmap -dump:format=b,file=/Users/mousycoder/Desktop/a.bin 9366 生成dump文件
    -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/mousycoder/Desktop/
    jhat /Users/mousycoder/Desktop/java_pid9783.hprof 圖形分析Heap
    select s.toString() from java.lang.String s where (s.value != null && s.value.length > 1000 )
  6. jstack 線程快照(虛擬機內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,主要用于定位線程問題)
    shutdownHook 在關(guān)閉之前執(zhí)行的任務(wù)
    jstack -l -F pid 強制輸出

線程狀態(tài)

  1. NEW
  2. RUNNABLE
  3. BLOCKED 一個正在阻塞等待一個監(jiān)視器的線程處于這個狀態(tài)(Entry Set)被動的阻塞
  4. WAITING 一個正在無限期等待另一個線程執(zhí)行一個特別的動作的線程處于這一狀態(tài) (Wait Set)主動顯式申請的阻塞
  5. TIMED_WAITING 一個正在限時等待另一個線程執(zhí)行一個動作的線程處于這一狀態(tài)
  6. TERMINATED 線程完成一個excution

JConsole

基于 JMX 的可視化監(jiān)視、管理工具
開啟 JMX 端口
nohup java -Xms800m -Xmx800m -Djava.rmi.server.hostname=192.168.1.250 -Dcom.sun.management.jmx
remote.port=1111 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar hc-charging-
server.jar &

互聯(lián)網(wǎng)開發(fā)流程



Jconsole 內(nèi)存分析思考過程


FullGC

Minor GC:當(dāng) Eden 區(qū)滿,觸發(fā) Minor GC
FullGC:

  1. 調(diào)用System.gc() 建議虛擬機進(jìn)行 Full GC,可通過 -XX:+DisableExplicitGC 來進(jìn)制 RMI 調(diào)用System.gc()
  2. 老年代空間不足 大對象直接進(jìn)入老年代,長期存活的對象進(jìn)入老年代,當(dāng)執(zhí)行 Full GC后空間仍然不足,則拋出 OutOfMemoryError,為了避免上面原因引起 Full GC,調(diào)優(yōu)時盡量做到讓對象在 Minor GC 階段被回收,讓對象在新生代多存活一段時間以及不要創(chuàng)建過大的對象和數(shù)組
  3. 空間分配擔(dān)保失敗 使用復(fù)制算法的 Minor GC 需要老年代的內(nèi)存空間作為擔(dān)保,如果出現(xiàn)了 HandlePromotionFailure 擔(dān)保失敗,則會觸發(fā) Full GC
    建議:
  4. 減少-Xmx大小,縮短 GC 時間(堆內(nèi)存設(shè)置越大,F(xiàn)ull GC 時間越長,停頓時間也會越長)
  5. 集群部署

互聯(lián)網(wǎng)問題

  1. 白名單問題
    解決方法:list.contain->set.contain->布隆過濾器(用戶量大和用戶量小系統(tǒng)解決方案不一樣)
  2. 死鎖
    解決方法:jstack 以及 new thread帶上名稱
  3. 堆內(nèi)存泄露
    FullGC 出現(xiàn)正常頻率為一天 1~2 次
    解決方案:jmap , heap dump on oom + jhat
  4. 堆外內(nèi)存泄露
    heap堆使用率很低,但是有 OOM 以及 Full GC
    解決方法:btrace

學(xué)習(xí)秘籍

  1. 知識體系
  2. 面試前看下知識體系導(dǎo)圖
  3. 堅持就勝利
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別 內(nèi)存溢出:out of memory,是指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用,...
    Aimerwhy閱讀 800評論 0 1
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時不同JDK版本的...
    高廣超閱讀 16,042評論 3 83
  • JVM架構(gòu) 當(dāng)一個程序啟動之前,它的class會被類裝載器裝入方法區(qū)(Permanent區(qū)),執(zhí)行引擎讀取方法區(qū)的...
    cocohaifang閱讀 1,825評論 0 7
  • http://www.cnblogs.com/angeldevil/p/3801189.html值得一看 Clas...
    snail_knight閱讀 1,610評論 1 0
  • Java和C++之間有一堵由內(nèi)存動態(tài)分配和垃圾收集技術(shù)所圍成的“高墻”,墻外面的人想進(jìn)來,墻里面的人想出來。 對象...
    胡二囧閱讀 1,324評論 0 4

友情鏈接更多精彩內(nèi)容