005-Java垃圾回收GC和JVM性能調優

Java知識點總結系列目錄

1. 垃圾回收
在這裏插入圖片描述

1.1 對象是否需要回收的兩種算法

  • 引用計數法

    爲每個對象添加一個引用計數器,用來統計指向該對象的引用個數。一旦某個對象的引用計數器爲0,則說明該對象已經死亡,便可以被回收了

    如果有一個引用,被賦值爲某一對象,那麼將該對象的引用計數器+1。如果一個指向某一對象的引用,被賦值爲其他值,那麼將該對象的引用計數器 -1

    引用計數法是一種古老的方式,除了需要額外的空間來存儲計數器,以及繁瑣的更新計數器以外;引用計數法還有一個重大的漏洞:無法處理相互引用,如圖中Object 6和Object 7的情況

  • 可達性分析法

    將一系列被稱爲GC Roots的變量作爲初始的存活對象合集,然後從該合集出發,所有能夠被該集合引用到的對象,並將其加入到該集合中,而不能被該合集所引用到的對象,並可對其宣告死亡。是現代虛擬機採用的方式。

    GC Roots是一些由堆外指向堆內的引用,如線程本地變量表中的引用變量,靜態變量和本地方法棧中的變量等

    如圖中Object 1-4位需要保留的對象,Object 5-7則沒有GC Roots類的引用可達,告訴GC可以進行垃圾回收

1.2 分代回收
在這裏插入圖片描述
上圖爲默認堆內存的邏輯構成。總體分爲新生代和老年代兩塊區域,默認比例爲 新:老=1:2。新生代又分爲Eden,Survivor From和Survivor To三個區域,默認比例爲 Eden:S0:S1=8:1:1

  • Minor/Young GC

    Minor/Young GC也就是普通最頻繁的GC發生在Eden和Survivor From區。Survivor From和Survivor To兩塊區域的角色將會發生調換,誰空誰是To。採用的算法就是複製交換算法。

    詳細過程:
    1)新new的對象分配在Eden區域
    2)當Eden佔滿時發生GC。根據可達性分析法發生GC後,將需要保留的對象複製到Survivor From區域,並將保留的對象的分代年齡加1(對象的分代年齡保存在對象頭中的Mark Word中),然後將Eden區域的對象全部回收銷燬。
    2) 再一次GC時將Eden和Survivor From作爲回收區域,將需要保留的對象複製到Survivor To區域,每次倖存下來分代年齡都將加1,然後將Eden和From區域的對象全部回收
    3)From和To互換角色,To區域永遠是Survivor中空的那一塊區域。

  • Full/Major GC

    Full/Major GC發生在老年代中,垃圾回收時將伴隨着STW(Stop the World)。過程中將會暫停其他線程的運行,時間相對比較長,應儘量避免發生此類型的GC。

    當分代年齡達到15(默認值,可以修改)時,對象將被放入老年代。除了分代年齡達到限制的對象被放入老年代,還有一些大對象(年輕代放不下,或者說超過了S0一半空間大小)將會直接放入老年代。當老年代空間被佔滿時將發生Full/Major GC。

    老年代回收算法採用標記清除

2. JVM性能調優

  • 目標

    JVM調優的目標是用較小的內存佔用來獲得較高的吞吐量或者較低的延遲。通過以下幾個指標來進行查看分析

    1)內存佔用:程序正常運行需要的內存大小。
    2)延遲:由於垃圾收集而引起的程序停頓時間。
    3)吞吐量:用戶程序運行時間佔用戶程序和垃圾收集佔用總時間的比值。

    結合實際場景,找到性能瓶頸進行分析來達到程序的目標。這裏有篇博文可以參考一下,裏面還有一些實例分析https://www.cnblogs.com/csniper/p/5592593.html

  • 常見關注點

    JVM參數設置上的常見關注點

    1)在設置程序堆內存的最大(-Xmx)最小(-Xms)值是儘量設置成一樣,防止Full GC提前和JVM動態調整堆大小浪費系統資源(防止內存抖動)。

    2)新生代儘量設置大些,讓對象儘量在新生代中存活時間長點,防止或延遲對象進入老年代的機會,以減少應用程序發生Full GC的頻率(減少STW-Stop the World影響用戶體驗)。當然這個設置還是需要根據實際的代碼進行分析,可以根據垃圾回收部分的內容進行分析設置

    3)如果對時延要求較高的服務,用戶線程不允許長時間的停頓的業務場景可以設置老年代的垃圾回收採用CMS收集器,通過參數“-XX:+UseConcMarkSweepGC”進行設置,此時老年代區域的空間相對比較大,新生代空間比較小。缺點是服務長時間運行,造成嚴重的內存碎片化

    程序實現上常見關注點

    1)避免創建過大的對象及數組:過大的對象或數組在新生代沒有足夠空間容納時會直接進入老年代,有可能導致頻繁的Full GC

    2)避免同時加載大量數據,如從數據庫或文件中讀取數據時,可以分批讀取,用完儘快清空引用

    3)當集合中有對象的引用,這些對象使用完之後要儘快把集合中的引用清空,這些無用對象儘快回收避免進入老年代

    4)儘量避免長時間等待外部資源(數據庫、網絡、設備資源等)的情況,縮小對象的生命週期,避免進入老年代,如果不能及時返回結果可以適當採用異步處理的方式等

    5)可以在合適的場景(如實現緩存)採用軟引用、弱引用,比如用軟引用來爲obj分配實例:SoftReference obj=new SoftReference(); 在發生內存溢出前,會將obj列入回收範圍進行二次回收,如果這次回收還沒有足夠內存,纔會拋出內存溢出的異常。

    6)避免產生死循環,產生死循環後,循環體內可能重複產生大量實例,導致內存空間被迅速佔滿

  • 常用分析工具

    通過參考的數據有系統運行日誌、堆棧錯誤信息、gc日誌、線程快照、堆轉儲快照等信息進行分析調優。可以藉助以下常用工具進行分析

    1)jps(JVM process Status)可以查看虛擬機啓動的所有進程、執行主類的全名(-l)、JVM啓動參數(-v)

    jps -lv
    

    2)jstat(JVM Statistics Monitoring Tool)監視虛擬機信息

    jstat -gc <pid> 500 10
    

    每500毫秒打印一次Java堆狀況(各個區的容量、使用容量、gc時間等信息),打印10次

    3)jmap(Memory Map for Java)查看堆內存信息

    jmap -histo <pid>
    

    打印出當前堆中所有每個類的實例數量和內存佔用,如下,class name是每個類的類名([B是byte類型,[C是char類型,[I是int類型),bytes是這個類的所有示例佔用內存大小,instances是這個類的實例數量

    jmap -dump:format=b,file=<filepath> <pid>
    

    轉儲堆內存快照到指定文件,文件格式一般是二進制的.hprof格式文件。快照文件可以使用jhat,jvisualvm,eclipse mat等工具進行加載查看

    4)jconsole,jvisualvm 可視化的界面展示JVM的參數,內存,線程和GC等等信息

    5)jhat(JVM Heap Analysis Tool) 命令來分析內存快照

    jhat -port <port> -J-Xmx4G <filepath>
    

    以指定端口port啓動 jhat 內嵌的服務器來分析.hprof二進制文件

    內存快照可以使用jmap導出,也可以使用參數“-XX:+HeapDumpOnOutOfMemory”在程序發生內存溢出時dump出當前的內存快照

    6)jstack,查看JVM線程快照。可以用來檢查死鎖,統計線程數量和具體分析進程中的某些線程狀態。

    jstack <pid>
    
  • 常用參數

    參數 說明 實例
    Xms 初始堆大小,默認物理內存的1/64 -Xms512M
    Xmx 最大堆大小,默認物理內存的1/4 -Xms2G
    Xmn 新生代內存大小,官方推薦爲整個堆的3/8 -Xmn512M
    Xss 線程堆棧大小,jdk1.5及之後默認1M,之前默認256k -Xss512k
    XX:NewRatio=n 設置新生代和年老代的比值。如:爲3,表示年輕代與年老代比值爲1:3,年輕代佔整個年輕代年老代和的1/4 -XX:NewRatio=3
    XX:SurvivorRatio=n 年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:8,表示Eden:Survivor=8:1:1,一個Survivor區佔整個年輕代的1/8 -XX:SurvivorRatio=8
    XX:PermSize=n 永久代初始值,默認爲物理內存的1/64(JDK1.7,1.8之後爲元空間-XX:MetaspaceSize) -XX:PermSize=128M
    XX:MaxPermSize=n 永久代最大值,默認爲物理內存的1/4(JDK1.7,1.8之後爲元空間-XX:MaxMetaspaceSize) -XX:MaxPermSize=256M
    verbose:class 在控制檯打印類加載信息
    verbose:gc 在控制檯打印垃圾回收日誌
    XX:+PrintGC 打印GC日誌,內容簡單
    XX:+PrintGCDetails 打印GC日誌,內容詳細
    XX:+PrintGCDateStamps 在GC日誌中添加時間戳
    Xloggc:filename 指定gc日誌路徑 -Xloggc:/data/jvm/gc.log
    XX:+UseSerialGC 年輕代設置串行收集器Serial
    XX:+UseParallelGC 年輕代設置並行收集器Parallel Scavenge
    XX:ParallelGCThreads=n 設置Parallel Scavenge收集時使用的CPU數。並行收集線程數。 -XX:ParallelGCThreads=4
    XX:MaxGCPauseMillis=n 設置Parallel Scavenge回收的最大時間(毫秒) -XX:MaxGCPauseMillis=100
    XX:GCTimeRatio=n 設置Parallel Scavenge垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n) -XX:GCTimeRatio=19
    XX:+UseParallelOldGC 設置老年代爲並行收集器ParallelOld收集器
    XX:+UseConcMarkSweepGC 設置老年代併發收集器CMS
    XX:+CMSIncrementalMode 設置CMS收集器爲增量模式,適用於單CPU情況。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章