面試官:JVM 這些我必問

JVM 內存分布

  • 線程共享數據區:

方法區->類信息,靜態變量
堆->數組對象

  • 線程隔離區

虛擬機棧-> 方法
本地方法棧->本地方法庫 native

  • 堆、程序計數器
  • JVM 運行數據

程序計數器

線程隔離 ,比較小的內存空間,當前線程所執行的字節碼的行號
線程是一個獨立的執行單元,由 CPU執行
唯一沒有 OOM 的地方,由虛擬機維護,所以不會出現 OOM

虛擬機棧

執行的是Java方法

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

本地方法棧

執行的是 native 方法的一塊 java內存區域,一樣有棧幀
hotspot將 Java 虛擬機棧和本地方法棧合二為一
jvm標準是 java 虛擬機棧和本地方法棧分開

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

方法區

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

運行時常量池

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

StringTable是HashSet結構
方法區的一部分,受到方法區的限制,依然會 OOM

Java 對象創建過程


-> static方法 static代碼塊

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

1) 指針碰撞(內存比較整齊)

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

  1. 將分配到內存空間都初始化零值
  2. 設置對象頭相關信息 (GC分代年齡、對象的HashCode、元數據信息)
  3. 執行方法

Java 對象內存布局

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

Java 對象的訪問

  1. 直接指針訪問

  1. 句柄訪問


對比:

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

對象在 JVM 過程

新生代:對象在 Eden 區出生后,當 Eden 區滿時,發生一次 Minor GC后,如果對象還存活且能被s1區域容納,使用復制算法將存活對象復制到s1,然后一次性清除 (Eden + s0) (此時對象年齡 +1,年齡達到 15 后進入老年代)然后將 s0 ,s1互換位置,最終得到一個空的新生代(Eden + s0, s1一直處于非活動被動狀態)

新生代、老年代、永久代

-Xmn 設置年輕代 -XX:NewRatio 設置年輕代與老年代的大小比例
-XX:OldSize 設置老年代大小
-XX:MaxPermSize 設置永久代(方法區)
-Xmx -Xms 設置堆空間大小

垃圾回收算法

引用計數器


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

標記-清除


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

標記-復制


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

標記-整理


老年代不適合復制算法

  1. 復制操作增多 2. 額外 50%空間浪費 3. 經常需要額外的空間分配擔保 4.可能老年代中對象 100% 存活

步驟:

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

垃圾收集器

Serial 收集器(新生代收集器)


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

ParNew 收集器(新生代收集器)

Serial收集器的多線程版本

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

Parallel Scavenge 收集器(新生代收集器)

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

Serial Old 收集器(老年代收集器)

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

Parallel Old 收集器(老年代收集器)

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

CMS 收集器(老年代+永久代收集器)

獲取最短回收停頓時間為目標的收集器,采用“標記-清除”算法,用于互聯網、B/S 系統重視響應的系統
觸發閾值:老年代或永久代達到 92%
-XX:+CMSClassUnloadingEnabled 讓 CMS 可以回收 Perm 區

步驟:

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

缺點 :

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

G1 收集器

面向服務端應用的垃圾收集器

Region->Remembered Set (解決 循環引用 )
檢查 Reference (程序對reference類型寫操作,檢查 reference 引用類型)
步驟:

  1. 初始標記 —— 標記 GC Roots 能直接關聯到的對象
  2. 并發標記 —— 從 GC Root 開始對堆中對象進行可達性分析,找出存活對象 ,這一階段耗時較長,但可與用戶程序并發執行
  3. 最終標記(Remembered Set Logs->Remembered Set)—— 修正在并發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程 Remembered Set Logs里面,最終標記階段需要把 Remembered Set Logs的數據合并到 Remembered Set中
  4. 篩選回收(Live Data Counting and Evacuation)—— 只需要掃描 Remembered Set

優勢:

  1. 基于“標記-整理” 為主和 Region 之間采用復制算法實現
  2. 可預測停頓,降低停頓時間,但G1 除了追求低停頓外,還能建立可預測的停頓時間模型
  3. G1 直接對 Java 堆中的 Region 進行回收(新生代、老年代不再物理隔離,他們都是一部分 Region)
  4. 可預測的停頓時間模型,G1 跟蹤各個 Regions 里面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在后臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的 Region

堆內存分配

Java 堆分布圖

對象分配的規則:

  1. 對象主要分配在新生代的 Eden 區 ( Eden區,From 區存活對象復制到 To區,Eden區,From區被回收,然后 To區和From區交換,再進行下一次垃圾回收 )
  2. 如果啟動了本地線程分配緩沖,將按線程優先在 TLAB 上分配
  3. 少數情況下也可能直接分配到老年代 (放不下From和To區的都直接放到老年代)

大對象分配

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

逃逸分析和棧上分配

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

虛擬機調優命令

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

  1. jinfo 27660 查看和調整進程虛擬機(未被顯示指定的)參數信息
  2. jmap 生成堆轉儲快照 -XX:+HeapDumpOnOutOfMemoryError

jmap -heap 9366;
jmap -histo 9366 | more; 顯示堆中對象統計
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 )

  1. jstack 線程快照(虛擬機內每一條線程正在執行的方法堆棧的集合,主要用于定位線程問題)

shutdownHook 在關閉之前執行的任務
jstack -l -F pid 強制輸出

線程狀態

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

JConsole

基于 JMX 的可視化監視、管理工具
開啟 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 &

互聯網開發流程

Jconsole 內存分析思考過程

FullGC

Minor GC:當 Eden 區滿,觸發 Minor GC
FullGC:

  1. 調用System.gc() 建議虛擬機進行 Full GC,可通過 -XX:+DisableExplicitGC 來禁止 RMI 調用System.gc()
  2. 老年代空間不足 大對象直接進入老年代,長期存活的對象進入老年代,當執行 Full GC后空間仍然不足,則拋出 OutOfMemoryError,為了避免上面原因引起 Full GC,調優時盡量做到讓對象在 Minor GC 階段被回收,讓對象在新生代多存活一段時間以及不要創建過大的對象和數組
  3. 空間分配擔保失敗 使用復制算法的 Minor GC 需要老年代的內存空間作為擔保,如果出現了 HandlePromotionFailure 擔保失敗,則會觸發 Full GC

建議:

  1. 減少-Xmx大小,縮短 GC 時間(堆內存設置越大,Full GC 時間越長,停頓時間也會越長)
  2. 集群部署

互聯網問題

  1. 白名單問題

解決方法:list.contain->set.contain->布隆過濾器(用戶量大和用戶量小系統解決方案不一樣)

  1. 死鎖

解決方法:jstack 以及 new thread帶上名稱

  1. 堆內存泄露

FullGC 出現正常頻率為一天 1~2 次
解決方案:jmap , heap dump on oom + jhat

  1. 堆外內存泄露

堆外內存( Unsafe的 allocateMemory ,ByteBuffer.allocateDirect,Runtime.getRuntime().freeMemory()可以查看不占用heap堆大小)
heap堆使用率很低,但是有 OOM 以及 Full GC
解決方法:btrace 跟蹤DirectByteBuffer構造函數,非 Java層面使用 Google Perftools分析

  1. 不對等數據

解決方案: MQ

本文由博客一文多發平臺 OpenWrite 發布!

你可能感興趣的

广东25选5开奖结果