目錄
- 往期博客:Java課堂篇3_初識JMM、常量池簡單理解(字串常量池、靜態常量池、大整型常量池)
- 為什么要了解垃圾收集和記憶體分配?
- 如何判斷物件已死?
- 參考計數演算法
- 可達性分析演算法
- JDK1.2之后參考的擴充
- 回收方法區
- 垃圾收集演算法分代收集理論
- 標記清除
- 標記復制
- 標記整理
- 物件分配
- 虛擬機性能監控故障處理工具
1、為什么需要了解垃圾收集和記憶體分配?
當需要排查各種記憶體溢位、記憶體泄露問題時,當垃圾收集成為系統達到高并發量的瓶頸時,我們必須對這些“自動化”的技術實
施必要的監控和調節,
2、如何判斷物件已死?
2.1、參考計數法
- 在物件中添加一個參考計數器,每當有一個地方參考它時,計數器值就加1,當有一個地方取消參考它時,計數器值減1
- 雖然額外占用記憶體空間,但是他的原理簡單,判定效率也很高
- Java領域主流的虛擬機未采用沒有選擇用,因為這個看似簡單的演算法有很多例外的情況要考慮,必須配合額外的大量處理才能確保正確作業,如單純的參考計數很難解決物件之間的互相參考問題,
2.2、可達性分析
- 通過一系列的GC Roots的根物件作為起始點集,從這些結點開始,根據參考關系向下搜索,搜索走過的路徑稱為參考鏈,如果某個物件到GC Roots間沒有任何參考鏈相連,或者用圖論的話來說就是不可達,證明這個物件不再被使用
- 固定可以作為GC Roots的物件包括:
- 虛擬機堆疊參考的物件(方法引數、區域變數、臨時變數)
- 方法區的靜態屬性參考的物件
- 方法區的常量池參考的物件
- 本地方法堆疊Native方法參考的物件
- 虛擬機內部的參考
- 同步鎖synchrionized持有的物件
2.3、JDK1.2之后參考的擴充
四種新擴充的參考
- 強參考:傳統的"參考"定義如
Object o = new Object(),只要強參考的關系還在,垃圾收集器就永遠不會回收掉被參考的物件 - 軟參考:描述還有用,但是非必須的物件,在系統將要發生記憶體溢位例外前,會把這些物件列入回收范圍之中進行二次回收,如果回收之后還是記憶體不夠,就會拋出例外,JDK1.2版之后提供了SftReference類來實作軟參考
- 弱參考:也是描述那些非必須的物件,但是它的強度比弱參考更弱一些,被弱參考參考的·物件只能生存到下一次垃圾收集發生為止,
- 虛參考:不對物件存活造成影響,位一各物件設定虛參考的目的只是為了垃圾收集器回收該物件時收到有個系統通知,
3、回收方法區
- 相比于堆記憶體的回收,方法區的回收由于苛刻的回收條件,其區域垃圾收集的成果往往很低
- 方法區的垃圾收集主要涉及兩部分的內容:
- 廢棄的常量:字串常量池里面的常量等
- 不在使用的型別:類、介面、方法、欄位的符號參考等
- 判斷一個類不在使用需要考慮
- 該類的所有實體都已經被回收
- 加載該類的類加載器已經被回收
- 該類對java.lang.Class物件沒有任何地方被參考,無法在任何地方通過反射訪問該類的方法
- 在大量使用反射、動態代理、CGLIB等位元組碼的框架,動態生成JSP等這類頻繁自定義類加載器的場景,通常都需要Java虛擬機具備型別卸載的能力,以保證不會對方法區造成過大的記憶體壓力,
4、垃圾收集演算法
垃圾收集演算法可以劃分為
- 參考計數式垃圾收集(對應前面的 參考計數演算法)
- 追蹤式垃圾收集(對用前面的 可達性分析演算法)
分代收集設計原則
- 弱分代假說:任何物件都是朝生夕滅
- 強分代假說:熬過越多次垃圾收集的物件就越難消亡
因此存在
- 部分收集Partical GC
- 新生代收集Young GC
- 老年代收集Old GC
- 混合收集Mixed GC
- 整堆收集Full GC
4.1、標記清除演算法
演算法分為 標記 和 清除 兩個階段
- 標記:首先標記出所有要回收的物件(或者存活的物件),在標記完成后
- 清除:統一回收掉被標記的物件
缺點
- 執行效率不太穩定:如果Java堆中包含大量物件,而且其中大部分都是需要回收的,這時需要大量標記和清除操作
- 記憶體空間碎片化問題:清除之后,產生大量不連續的記憶體空間,當需要分配大物件是,有可能放不下還需要進行一次GC
4.2、標記復制演算法
相比標記清除演算法
- 半區復制:將記憶體容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊記憶體用完了,就還將存活的物件復制到另外一塊,再把使用過的一塊記憶體全部回收清理,
- 缺點:這種演算法產生大量的記憶體空間復制的開銷,可用記憶體縮小為原來的一半,空間浪費,
半區復制分代策略
- Appel式回收:虛擬機的新生代使用此種回收演算法,將新生代分為 Eden和Survivor區域,比例為8:1:1;每次將Eden的存活物件放到Survivor中
- 需要老年代記憶體擔保,也就是Survivor需要像老年代傳送物件,
4.3、標記整理演算法
相比于新生代使用標記復制演算法、標記清除演算法
- 老年代不能使用標記復制演算法,因為老年代物件存活率高,進行復制會浪費記憶體空間
- 針對老年代物件的死亡特征,需要使用標記-整理演算法,區別于標記-清除演算法本質區別就是 不是直接對可回收物件清理,而是將存活的物件往一段移動,清除邊界以外的記憶體,
特點
- 移動存活物件的·時候,尤其是老年代,移動物件地址需要全部用戶程式才能進行Stop The World
- 不移動時候采用標記清除演算法,記憶體分配復雜
- 移動的時候采用標記整理演算法,回收時更復雜
- 有一種和稀泥方式,碎片化程度到達一定程度開啟移動
5、物件分配
物件的記憶體分配從概念上講都是堆上分配,(實際可能有即時編譯后被拆散為標量型別并間接的在堆疊上分配),在經典分代的設計下,新生物件會直接分布在新生代,一些超過閾值的大物件可以直接分布在老年代
- 物件優先在Eden分配:沒有足夠空間會觸發YoungGC,存活的物件會進入Survivor
- 大物件直接進入老年代:為了避免大物件記憶體復制的開銷,直接將大物件分配到老年代,通過設定
-XX:PretenureSizeThreshold=3145728引數指定閾值 - 長期存活的物件進入老年代:虛擬機給每個物件定義了一個物件年齡(Age)計數器存盤在物件頭中,物件通常在Eden單上,如果第一次經理YongGC能夠存活下來并且Survivor能夠放的下,該物件就會被移動到Survivor并且年齡加1歲,當年齡增加到一定程度(默認15歲)就會被放到年代,可以通過
-XXMaxTenuringThreshold設定 - 動態物件年齡判斷:除了物件年齡增長進入老年代,如果Survivor空間相同年齡的物件綜合大于Survivor空間的一半,年齡大于等于該年齡的物件可以直接進入老年,
- 空間分配擔保:在發生YoungGC的時候,虛擬機必須先檢查老年代最大可用的連續空間是否大于新生代所有物件的總空間,
- 如果這個條件成立,那么這一次GC是確保安全的
- 如果這個條件不成立,則虛擬機會檢查引數
-XX:HandlePromotionFailure引數是否允許擔保失敗,如果允許,則會檢查老年代可用連續空間是否大于歷屆上升到老年代物件年齡的平均大小- 如果大于,進行一次YoungGC
- 如果小于,或者是
-XX:handlePromotionFailure設定不允許冒險,這時候就需要進行一次FullGC
6、虛擬機性能監控故障處理工具
6.1基礎故障處理工具
-
jsp:虛擬機行程狀況工具
虛擬機行程查看定位工具

-
jstat:虛擬機統計資訊監視工具
顯示類加載、記憶體、垃圾收集器、即時編譯等運行時資料,定位虛擬機性能問題
引數參考:https://blog.csdn.net/ouyang111222/article/details/53688986 -
jinfo:Java配置資訊工具
-
jmap:Java記憶體映像工具
用于生成堆轉儲快照 -
jhat:虛擬機堆轉儲快照分析工具
-
jstack:Java堆疊跟蹤工具
-
jcmd:Java7開始提供的虛擬機診斷命令工具
基本Java工具
- javadoc:API檔案生成器
- javap:位元組碼分析工具
6.2可視化故障處理工具
- jhsdb:Java9開始提供的行程除錯器,基于服務代理的除錯工具
首先使用jps查看行程號,然后jhsdb hsdb --pid xxx進行操作 - JConsole:Java監視與管理控制臺
控制臺輸入jconsole,本地、遠程進行連接 - VisualVM:多合-故障處理工具
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/298925.html
標籤:其他
