JVM學習筆記

java內存區域

  • java內存區域劃分爲:java堆、方法區(以及運行時常量池)、本地方法棧、虛擬機棧、程序計數器
  • 線程共享的區域是:java堆、方法區;線程隔離的是:本地方法棧、虛擬機棧、程序計數器。
  • 異常:程序計數器既不拋出StackOverFlow也不拋出OutOfMemoryError;java堆拋出OutOfMemory;本地方法棧和虛擬機棧兩者都拋出;方法區拋出OutOfMemoryError
  • 本地方法棧和虛擬機棧生命週期與線程一致,每個方法運行時都會創建一個棧幀。
  • JDK 1.7中,常量池從方法區中移除,到了堆中;而JDK 1.8移除了方法區,取而代之的是元空間(MetaSpace) 元空間和永久代類似,區別在於元空間不在虛擬機中,而是在本地內存中,所以理論上是隻受本地內存的約束;當然實際上還是可以通過參數來設定它的大小

HotSpot

  • 對象分配保證原子性:採用CAS+失敗重試的方式
  • 訪問對象的方式:採用直接指針訪問,而非句柄訪問。
  • HotSpot中不區分本地方法棧和虛擬機棧
  • HotSpot採用可達性算法確定對象是否應該被回收,而不是引用計數法,避免重複引用
  • 採用OOPMap(普通對象指針映射)來達到準確式GC

JVM參數

  • -Xms:設定堆的最小值
  • -Xmx:設定堆的最大值,如-Xmx=-Xmx,則堆不能自動擴展
  • -Xss:設定棧的大小
  • -Xoss:設定本地方法棧大小(存在但無效)
  • -XX:PermSize/-XX:MaxPermSize  設定方法區大小.
  • -XX:MaxDirectMemorySize:設定直接內存的大小(NIO常用)
  • -XX+UseConcMarkSweepGC:使用CMS收集器GC,同時將ParNew作爲默認的新生代收集器
  • -XX:MaxGCPauseMillis:最大垃圾收集時間
  • -XX:GCTimeRRatio:設置吞吐量大小(運行用戶代碼時間/(運行用戶代碼時間+GC時間))
  • -XX:UseAdaptiveSizePolicy:設置自適應策略,通過設置-XX:MaxGCPauseMillis(更關注停頓時間)或-XX:GCTimeRRatio(更關注吞吐量),給虛擬機設定一個優化目標方向。
  • -XX:CMSInitiatingOccupancyFraction:CMS收集器GC觸發的老年代佔用率閾值
  • -XX:PretenureSizeThrreshold設定大對象定義的大小閾值
  • -XX:MaxTenuringThershold:“長壽對象”進入老年代的年齡閾值

內存溢出

  • 棧存在兩種異常OOM和SOF,但在單線程情況下只會出現StackOverFlow(SOF),只有在多線程情況下才會出現OOM,且每個線程分配的棧空間越大,則可建立的線程數量越少,越容易出現OOM。

對象相關

  • HotSpot虛擬機創建對象過程:類加載檢查->內存分配->內存空間初始化爲0值->對對象進行必要的設置(對象頭)
  • 對象可以分爲三個部分:對象頭、實例數據、對齊填充。對象頭存儲對象包括本身運行時的屬性,比如哈希碼,鎖狀態標誌;另一部分是類型指針;實例數據時對象真正存儲的信息;對齊填充則是,如果對象的大小不爲8的倍數,則填充爲8的倍數(HotSpot要求對象大小必須是8的整數倍)。
  • 對象的訪問包括:句柄訪問/直接指針兩種,HotSpot使用直接指針。

異常

  • 堆:OOM,只要不斷的創建對象並保證可達性(HotSpot採用可達性算法確定對象是否應該被回收,而不是引用計數法,避免重複引用)
  • 棧(虛擬機棧/本地方法棧):OOM&SOF,在單線程情況下只會出現SOF,即調用的方法深度超過了設定的棧大小(遞歸過深);在多線程情況下能出現OOM,棧空間越大,那麼允許存在的線程數越少,則越有可能出現OOM
  • 方法區/運行時常量池:OOM。運行時動態創建大量類,常出現於各種框架搭配(SSM/SSH)使用字節碼動態代理技術(CGlib以及JDK動態代理)大量創建新的類。
  • 本機直接內存溢出:OOM。NIO技術,特點是在Heap Dump中沒有明顯的異常,入如果Dump文件很小,又直接或間接的使用了NIO,不妨考慮一下

垃圾收集器和內存分配策略

  • 在任何情況下都不建議採用finalize()方法去關閉資源。
  • finalize()方法優先級很低,系統對於任何一個對象的finalize()方法都只會調用一次。
  • 回收方法區(永久代):可以不要求在方法區實現垃圾收集:主要收集廢棄的常量和無用的類。比如前面提到的字符串常量"java"(sun.misc.Version)如果沒有String對象引用它,那麼它就是廢棄的,在必要情況下,它會被回收。而無用類進行回收主要出現在大量使用反射,動態代理、JSP、OSGi。

垃圾收集算法

  • 標記-清除算法:通過可達性算法標記對象;通過篩選進入F-Queue(覆蓋或實現了finalize()方法)再進行一次可達性分析標記。
  • 複製算法
  1. 優點:實現簡單,運行高效,不產生內存碎片,相對規整
  2. 缺點:有較大的內存浪費,在存活對象較多的情況下需要進行較多的複製操作,會降低效率,因而適合對象“朝生暮死”的區域(新生代)
  3. 適用:現代商業虛擬機都採用本算法回收新生代,將新生代劃分爲一塊較大的Eden和塊較小 的Survivor區。HotSpot默認Eden:Survior=8:1(其中一塊的大小)
  4. 操作:每次使用Eden和一塊Survior區,當GC時,將兩個區的存活對象一次性複製到另外一塊Survior區上,最後清理掉Eden區和開始那一塊Survior區的空間。
  5. 其他:如果Survior區空間不夠用,則會用老年代進行分配擔保(即老年代“借出”一部分空間給Survior區存放上一次GC存活下來的對象)

  • 標記-整理算法
  1. 優點:不需要內存其他區域分配擔保,內存區域相對規整
  2. 缺點:在標記-清除算法的基礎上多了一步整理操作,效率相對降低
  3. 適用:內存區域存在大量存活對象
  4. 操作:同標記-清除,在標記後將所有存活對象向一端移動,然後清除邊界以外的所有內存,獲得相對規整的內存,減少內存碎片

  • HotSpot實現

  • 枚舉根節點

  • 可達性分析的GC Roots選取:全局性引用(如靜態屬性/常量);執行上下文(棧幀中的本地變量表)
  • GC時要求同步、一致性(即GC時對象引用關係等不能再發生改變),因此GC時JAVA的執行線程都必須被停頓。
  • 主流的虛擬機都採用的準確式GC:即虛擬機有辦法知道內存中某個位置到底存的是數據還是引用的地址。

  • 安全點
  • 程序並非在所有的地方都能停頓下來開始GC,可以的點被稱爲安全點
  • 安全點記錄了OOPMap中引用的地址
  • 最明顯特徵:指令序列複用,例如方法調用、循環跳轉、異常跳轉等
  • 兩種方法使得所有線程(除JNI調用的)到安全點再停頓:主動式中斷、搶先式中斷(基本不用)
  • 主動式中斷:設定一個標誌,由線程自己去輪詢這個標誌,標誌與安全點重合

  • 安全區域
  • 對於安全點的補充:1、安全點針對的是運行期的線程,如果線程在Sleep狀態或Block狀態,那麼顯然線程無法“跑”到安全點再進行停頓。
  • 一段代碼片段中,引用保持一致性。在這個區域內的任意地方開始GC都是安全的,即安全區域內每個點都是安全點。
  • 首先表示自己(線程)已經進入了安全區域(Safe Region),這樣在GC時,標識爲SR的線程都不會管了;而在線程要離開SR時,需要檢查是否完成了根節點枚舉(或是已經完成了GC),如果完成了就可以離開,否則需要等到收到可以安全離開SR區域的信號爲止。

垃圾收集器

  • JDK 1.7  一共有7種收集器
  • 新生代收集器有:Serial、ParNew、Parallel Scavenge、G1(新生代和老年代共有)
  • 老年代收集器有:CMS、Serial Old(MSC)、Parallel Old、G1(共有)
  • Serial收集器
  1. 特點:單線程、收集時暫停其他線程,對新生代採用複製算法,老年代使用標記-整理算法;可與CMS收集器配合工作
  2. 優點:專注收集,有最高的單線程效率。簡單而高效
  3. 缺點:需要暫停其他所有的工作線程
  4. 適用:運行在Client模式下默認新生代收集器
  • ParNew收集器
  1. 特點:Serial收集器的多線程版本、可與CMS收集器配合工作
  2. 優點:多線程並行收集,在目前CPU多核情況下表現相對於Serial更好
  3. 缺點:在單線程效率上不如Serial,甚至在雙CPU情況下也不一定能超過。
  4. 適用:運行在Server模式下虛擬機默認新生代收集器
  • Parallel Scanvenge收集器
  1. 特點:吞吐量優先、高效利用CPU、自適應GC策略:-XX:UseAdaptiveSizePolicy
  2. 優點:高效利用CPU,和ParNew類似,都是並行多線程收集器
  3. 缺點:和ParNew類似
  4. 適用:手動優化困難時,對CPU吞吐量需求高,新生代收集器
  • Serial Old收集器
  1. 特點:在JDK1.5之前與Parallel Scanvenge搭配使用;作爲CMS收集器的後備預案在出現concurrent mode failure時使用
  2. 優點:同Serial
  3. 缺點:同Serial
  4. 適用:運行在Client模式老年代默認收集器
  • Parallel Old收集器
  1. 特點:Parallel Scanvenge的老年代版本,注重吞吐量;JDK 1.6纔出現
  2. 優點:略
  3. 缺點:略
  4. 適用:Server端老年代
  • CMS收集器
  1. 特點:採用標記-清除算法、GC線程同用戶線程併發執行;默認啓動GC線程數=(CPU+3)/4;不能等待老年代接近滿了再GC,需要提前
  2. 優點:併發、低停頓
  3. 缺點:1、對CPU資源非常敏感;2、CMS收集器無法處理浮動垃圾,可能出現concurrent mode failure從而導致FULL GC;3、因爲是基於標記-清除算法的,所以容易產生大量碎片
  4. 適用:對低停頓要求很高的應用,如BS服務端
  5. 名詞解釋:浮動垃圾——因爲CMS是併發的,所以會出現“邊掃地邊有人丟垃圾”的情況,這部分垃圾就叫做浮動垃圾;因爲此時初始標記已經完成了(已經進入了併發標記),所以這部分垃圾只能等待下一次GC。
  6. 操作:1、初始標記(標記GC roots的直接後繼)2、併發標記(進行GC Tracing,遍歷進行可達性分析,時間最長的步驟,但可併發執行)3、重新標記(即修正併發標記期間對象引用發生變化的那部分記錄)4、併發清除(GC)
  • G1收集器
  1. 特點:可預測的停頓時間、採用標記-整理算法、可用於新生代和老年代(因爲它在堆裏不分代,只是分成不同但大小相同的區域)、維護一個回收優先級列表(回收大小/回收時間);通過維護Remember Set來避免進行全堆掃描(其他收集器也是這樣)
  2. 優點:充分利用多CPU,多核環境;可預測停頓時間
  3. 缺點:目前還不夠穩定,JDK 1.7推出 用於取代CMS的地位
  4. 適用:與CMS類似,硬件性能強的服務端
  5. 名詞解釋:Remember Set——如果當前對象 (在Region(新生代/老年代)含有另外一個Region(代)的引用,那麼會把它記錄到這個集合裏面,最後將它加入GC roots,避免進行全堆檢索,降低Minor GC的效率
  6. 操作:類似於CMS。1、初始標記。2、併發標記。3、最終標記。4、篩選清除

內存分配與回收策略

  • 對象優先在Eden分配
  1. 對象優先在新生代Eden區分配,如果Eden區空間不夠,則會發起一次Minor GC
  • 大對象直接進入老年代
  • 大對象典型的就是長數組或長字符串
  • 大對象對虛擬機的內存分配是一個壞消息,“朝生暮死”的大對象尤是。通過參數-XX:PretenureSizeThrreshold設定大對象定義的大小閾值
  • “長壽”對象將進入老年代
  • 都叫老年代了,裏面對象如果年齡不算權重的話,有些名不副實。虛擬機給每個對象定義了一個“年齡”(每熬過一次GC年齡+1),如果年齡大於一定的界限值(默認是15),對象將進入老年代。這個閾值通過-XX:MaxTenuringThershold設置
  • 動態對象年齡判定
  • 有時候儘管年齡不夠,但“資歷”夠了,也能進入老年代。要求就是Survior區中相同年齡的所有對象大小超過了整個Survior區的一半,那麼這些對象就會進入老年代
  • 空間分配擔保
  1. 在進行Minor GC之前會進行一次檢查:判斷老年代中的最大連續可用控件是否大於新生代所有對象的總空間(因爲進行Minor GC最壞最極端的情況就是GC後所有對象仍然存活,而新生代採用的是複製算法,需要存活對象那麼大的後備輔助空間用以存儲這些存貨對象)
  2. 如果不夠,且HandlePromotionFailure被設置爲不允許失敗,那麼會發生Full GC;或允許失敗的情況下,但最大連續可用空間小於每次Minor GC後存活對象大小的平均值,也會發生Full GC。

調優案例分析

  • 高性能硬件上的部署策略
  1. 出現問題:每隔一段時間出現一次長時間GC
  2. 問題分析:控制full GC的關鍵是看大多數對象是否符合“朝生暮死 ”的新生代標準,不要出現過多大對象,尤其是成批量的大對象
  3. 目前主要有兩種方式:64bit JDK來使用大內存;使用多個32bit虛擬機建立邏輯集羣。
  • 64bit JDK
  1. 優點:可以充分利用高性能硬件,支持更高的內存分配。
  2. 缺點:目前而言,64位性能略低於32位。(2)需要保證程序足夠穩定,否則由於堆實在太大,難以對堆快照進行分析。(3)由於指針膨脹等原因,64位普遍比32位更消耗內存。
  • 多個32位JDK
  1. 優點:64位相對的缺點
  2. 缺點:(1)由於有多個邏輯虛擬機,所以容易產生併發磁盤I/O異常。(2)32位限制物理內存上線爲4G,堆最大隻能到1.5G。(3)資源利用率不高,尤其是珍貴的連接池。(4)大量使用了本地緩存,這個可以通過集中式緩存改善。
  3. 操作:無Session複製的親和式集羣(即規定某個Id用戶請求會話固定分配到某一個集羣節點上),在前端搭載負載均衡。
  • 集羣間同步導致的內存溢出
  • 堆外內存導致的溢出錯誤
  1. 直接內存溢出:通過-xx:MaxDirectMemorySize調整直接內存大小
  2. 線程堆棧(虛擬機棧/本地方法棧):SOF/OOM,通過-Xss調整棧大小。
  3. Socket緩存區:每個Socket連接都有Receive和Send兩個緩存區,如果連接太多,會拋出IO異常,too many open files
  4. JNI代碼:本地方法的內存也不在堆中
  5. 虛擬機和GC:.....    我要見師座!
  • 外部命令導致系統緩慢
  1. 比如shell、PHP等服務端腳本語言
  • 不恰當的數據結構
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章