深入理解 JVM 和 GC -- 內存調優

運行時數據區

  • 首先弄清楚一點:一個java進程開啓一個jvm,進程又可分成多線程運行
  • 進程在 jvm 上運行時,數據保存在運行時數據區,運行時數據區包括堆、方法棧、虛擬機棧、本地方法區和程序計數器,其中堆和方法棧是線程共享的,虛擬機棧、本地方法區和程序計數器是線程私有的
    運行時數據區

堆(線程共享)

  • 存儲程序運行時用到的所有對象,而線程虛擬機棧的局部變量表保存的是對象的引用指針;堆空間分爲新生代、老年代、元數據(jdk1.8之後,1.7之前稱爲永久代),元數據實際上不屬於堆空間
    JMM
  1. 爲什麼分代? 對象的生命週期不一樣,GC過後有些被回收有些存活下來,存活下來的對象也不必與新來的對象一起GC,需要另外空間來存放存活下來的對象

  2. 爲什麼Eden:servivor是8:1:1而不是9:1? Minor GC => 新生代 Major GC => 老年代 一次 Major GC 所花時間 > 一次 Minor GC 所花時間 所以我們希望新生代的對象儘量不要被放到老年代,即 GC
    之後應有98%的對象被回收,如果是9:1的話,對象很容易存活到老年代,所以希望對象在新生代進行更多次的GC以達到目標

  3. 對象分配比例配置:-XX:SurvivorRatio=8

新生代

  • 新生代佔堆空間的1/3,分爲Eden區(8/10)、From區(1/10)、To區(1/10)
  • 新創建的對象會被放入Eden區,當Eden區空間不足時,虛擬機會做一次 Minor GC,大部分對象會被垃圾回收器回收,剩下的存活的對象放入 From 區,這是複製回收算法
  • 當 From區滿時,進行 Minor GC 會對 From 區也做 GC,存活的會進入 To 區,此時 From 區與 To 區角色互換
  • 當 Eden 區再次滿的時候, Minor GC 會把存活下來的對象放入 To 區(因爲From 區與 To 區已經角色互換)

老年代

  • 當 Minor GC之後 新生代滿或放不下新對象時,會觸發擔保機制,把存活的對象放入老年區
  • 當整個堆空間放滿之後,會進行 Full GC,Full GC = Minor GC + Major GC,System.gc() 引起的是 Full GC

元空間

  • JDK1.7之前: 永久代
  • JDK1.8之後: 元空間(直接內存),這樣設計是爲了解決永久代可能溢出的問題,可以動態擴容,屬於堆外內存,可能會擠壓堆內內存,所以根據需求定義其大小

堆空間大小的設定

通過GC日誌獲取多次 full GC 存活下來的活躍數據的平均值
總堆大小 = 活躍數據 * 4
新生代 = 活躍數據 * 1.5
老年代 = 總堆大小 - 新生代
(虛擬機有默認值和自定調整)

內存規整

當多線程進行對Eden區存放堆數據的時候會出現線程安全問題(指針碰撞)
指針碰撞
棧上分配:線程在Eden區有自己的Buffere(可調整),可避免過多的鎖,當Buffere不足時會清除到堆內存

堆的調整

-Xms …m 堆的起始大小(start)
-Xmm …m 堆的最大值(max)
-Xmn …m 堆的新生代大小(new)
-Xss …m 每個線程的棧大小

方法區(線程共享)

線程共享,保存類信息、靜態變量、常量(jdk1.7)

程序計數器(線程私有)

  • 指向當前線程所執行的jvm指令的地址
  • 當線程掛起時,可以保存線程運行的狀態,以便下次繼續執行

虛擬機棧(線程私有)

  • 線程私有,保存當前線程運行方法所需要的數據、指令和返回地址,一個方法對應一個棧幀,每調用一個方法會壓入一個棧幀,方法執行完出棧
  • 每一個棧幀包含局部變量表、操作數棧、動態鏈接、方法出口等
    虛擬機棧

局部變量表

保存當前線程執行的方法的局部變量,寬度爲4字節(32位),當線程要對變量操作時,會從棧頂的局部變量表開始找,找不到再往下找

操作數棧

  • 保存線程執行指令相關的操作數
  • 操作數棧和局部變量表是緊密聯繫的,例如 int c = a + b 在虛擬機底層執行的指令:
    iload_1
    iload_2
    iadd
    istore_3
    指令的執行過程

動態鏈接

保存線程方法運行時引用的類庫方法的接口地址

方法出口

方法執行完成的返回地址,正常返回:return,異常:執行異常處理

本地方法棧(線程私有)

線程私有,保存nactive方法,底層用c/c++實現的、沒有實現類的方法

GC-垃圾回收

垃圾回收機制是針對堆空間中存放的對象數據的, 進行垃圾回收可以在一定程度上節省內存空間,以便放入更多的數據

判斷算法

引用計數法

給每一個對象增加一個被應用計數標誌以判斷該對象是否可被清楚,但可能會出現循環引用,所以JVM不用這個算法

可達性分析

GC Root:

  • 虛擬機棧中局部變量表引用的對象
  • 方法區中靜態變量和常量引用的對象
  • 本地方法棧中JNI引用的對象
    可達性分析
    當帶對象節點不可達時,會進入finalize()方法,不一定會立即被回收,看有沒有重寫finalize()方法

回收算法

標記-清除算法

對不可達的對象進行標記和清除,但是會產生內存碎片
標記-清除算法

複製-回收算法

對不可達對象進行回收,存活下來的對象放入servivor區
複製-回收算法

標記-整理算法

對不可達對象進行標記、清除和整理,因爲進行了在 GC 的時候整理所以不會產生內存碎片
標記-整理算法

垃圾集收器

  • 雖然每個進程開啓一個 jvm,但同一個 jdk 默認垃圾集收器相同
  • 新生代的垃圾集收器:serial / parNew / parallel 都用的是複製回收算法
  • 老年代的垃圾集收器:CMS(標記 - 清除) / serial Old(標記 - 整理) / parallel Old(標記 - 整理)
    垃圾集收器

新生代(用複製回收算法)

serial

單線程執行垃圾回收代碼,垃圾回收時其他線程不能執行,即應用線程停頓(stop-the-world)
serial

parNew

多線程執行垃圾回收代碼,垃圾回收時其他線程不能執行,在多核心CPU的情況下停頓時間會比 serial 少
parallel
配置線程個數:-XX:parallelGCThreads=n

parallel

主要關注吞吐量,吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾回收時間)

-XX:MaxGCPauseMillise=n // 控制GC停止時間
-XX:GCTimeRatio=n // 控制GC運行時間的比率
-XX:UseAdaptivesizePolicy // 開啓全局維護策略

老年代

CMS (用標記 - 清除算法)

  • 主要關注減小回收停頓時間(stop-the-world),但增加了回收時間,這是 CMS 與 parallel的區別
  • 先進行GC Root可達標記
  • 再進行併發標記,在併發標記的同時業務線程能併發運行
  • 接下來 stop-the-world 重新整理,因爲併發標記的過程用戶線程也在執行
  • 接着進行併發清除
    CMS
    -XX:CMSInititiongOccupancyFraction // 碎片整理
    -XX:+UseCMSCompactAtFullCollection // Full GC 時開啓壓縮整理
    -XX:CMSFullGCsBeforeCompaction // 設置多少次 Full GC 之後開啓壓縮

serial Old (用標記 - 整理算法)

CMS備用預案,觸發擔保機制後老年代空間不足時,會進行 Full GC

parallel Old (用標記 - 整理算法)

查看當前JVM的垃圾集收器

-XX:+PrintFlagsFinal
UseGC

-XX:+PrintCommandLineFlags
UseGC

GC 日誌

輸出日誌

-Xloggc: /…gc.log
分析 gc.log 的信息,例如:
解讀:[GC類型 GC前佔用空間 -> GC後佔用空間(總空間), GC耗時]
gc.log

-XX: +PrintGCTimeStamps
-XX: +PrintGCTimeStamps

-XX: +PrintGCDetails
-XX: +PrintGCDetails

日誌文件控制

-XX: -UseGCLogFileRotation
-XX: GCLogFileSize=8k

監視日誌文件命令

tail -f gc.log // GC日誌
tail -f catalina.log // 應用日誌

JDK自帶監控工具

jconsole // java 監視和管理控制檯
jconsole

jps // 拿到pid
jps

jmap -heap pid // heap使用情況
jmap -heap pid

jstat -gcutil pid 毫秒 // GC數據統計(Old GC沒有變化且還在增長 => 內存泄漏)
gcutil

jstat -gccause pid 毫秒 // 引發GC的原因
gccause

jvisualVM // 查看線程運行狀況和dump等
jvisualVM
dump

jstack pid > p.txt // 導出線程的dump

MAT

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/…error.hprof

  • 查找內存泄漏原因:
  • GC日誌: xxx -> xxx(xxx) 有沒有回收
  • MAT: Retained Heap 和 GC Root 指向
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章