面試官:JVM 這些我必問

JVM 內存分佈

  • 線程共享數據區:

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

  • 線程隔離區

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

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

程序計數器

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

虛擬機棧

執行的是Java方法數組

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

本地方法棧

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

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

方法區

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

運行時常量池

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

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

Java 對象建立過程


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

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

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

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

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

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 發佈!
相關文章
相關標籤/搜索