JVM垃圾收集器與內存分配策略學習總結

方法區:

1.線程共享
2.儲存類信息,常量,靜態變量,編譯器編譯後的代碼
3.非堆(別名)用於區分Java堆
4.不需要連續的內存
5.可以固定或可擴張
6.選擇不實現垃圾回收//這個區域很少進行垃圾回收
7.針對常量池回收
8.對類型的卸載
9.無法滿足內存分配需求拋出OutOfMemoryError4

虛擬機棧(VM Stack):

1.線程私有
2.生命週期和線程相同
3.描述Java方法執行的內存模型(每個方法執行會創建一個棧幀)
4.棧幀
5.線程請求深度大於虛擬機允許深度拋出StackOverFlowError
6.擴展無法申請到足夠內存拋出OutOfMemoryError

本地方法棧(Native Method Stack):

1.爲本地方法服務
2.區別於虛擬機棧: 爲虛擬機執行Java方法服務
3.拋出StackOverFlowError, OutOfMemoryError

Heap:

1.Java虛擬機所管理最大一片內存區域
2.線程共享
3.虛擬機啓動時創建
4.存放對象實例,數組
5.垃圾回收管理GC主要區: 收集器採用分代收集算法,
6.可劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer TLAB)
7.物理邏輯不連續,邏輯順序連續即可
8.可通過-Xmx和-Xms控制大小
9.堆無法擴展時拋出OutOfMemoryError

程序計數器:

1.較小內存空間
2.當前線程行號指示器
3.線程私有內存
4.唯一個沒有OutOfMemoryError區域

直接內存:

1.NIO中使用Channel,Buffer的IO方法,使用Native函數庫直接分配的堆外內存
2.會拋出OutOfMemoryError

對象創建:

1.類加載檢查機制
2.爲新生對象分配內存
3.把一塊確定大小的內存從堆中劃分出來:
4.將分配的空間初始化爲0(不包含對象頭)
5.對對象進行必要設置
6.屬於哪個類的實例
7.如何找到類的元數據信息
8.對象的哈希值
9.對象的GC分代年齡
10.這些數據存放在對象頭(Object Header)中
11.對象得到創建,但是所有字段爲0,在執行new之後立即執行方法

對象的內存佈局:

1.對象(Object Header):存儲對象自身運行時數據(Mark Word):哈希碼,GC分代年齡,鎖狀態標識,線程持有鎖,偏向線程ID,偏向時間戳
2.類型指針:對象指向它的類元數據的指針—虛擬機通過該指針確定該對象屬於哪個類; 不是所有的對象實例都保留有類型指針; 如果對象是數組,還必須有一塊用於記錄數組長度的數據
3.實例數據(Instance Data)真正存儲的數據: 儲存順序受到虛擬機分配策略參數(Fields Allocation Style)和字段在Java源代碼中定義順序的影響
4.對齊填充(Padding):僅起佔位作用

對象訪問定位

Java通過棧上引用(reference)數據來操作堆上具體對象
句柄訪問:
1.堆中劃分一塊內存作爲句柄池
2.reference中存儲對象的句柄地址
3.句柄中包含對象實例數據與類型數據各自的具體地址信息
優點:reference中存儲穩定的句柄地址,在對象移動時只會改變句柄中的實例數據指針

GC算法

早期GC算法:
引用計數法:對象獲得一個引用計數器,有地方引用,則記數器+一次,當計數器爲0表示對象不被引用,垃圾回收器進行回收過程; 但是無法解決對象之間循環引用
例: A obj1=new A();B obj2=new A();
obj1.a=obj2;obj2.a=obj1;obj1=null;obj2=null;
java採用可達性分析算法:
當對象到GC Roots沒有任何引用連接的時候,此對象是死緩狀態,被進行標記,若該對象沒有覆蓋finalize()/該方法已被調用過,則此對象在第二次被標記的時候移出回收集合,發生回收
引用------
強引用:強引用存在就不會被GC
軟引用:有用單非必須的對象,程序發生OOM之間進行二次回收,如果二次回收沒有足夠的內存,拋出OOM
弱引用:只能活到GC之前,GC時候無論內存夠不夠,都會被回收
虛引用:用於引用關聯對象被GC時會收到一個系統通知

回收方法區:
1.廢棄常量:常量池中一個常量沒有引用指向,GC時將會被內存回收
2.無用類:該類所有實例被回收,加載該類的ClassLoader被回收,Class對象沒有地方引用,不能通過反射訪問該類

GC算法:
1.標記清除算法:首先標記對象,GC時進行清除; 缺點:產生大量不連續的內存空間碎片
2.複製算法: 將內存劃分爲大小相等的兩塊內容,每次只使用其中的一塊,每次GC時將該內存上的對象移動到另一塊內存上,對使用過的空間進行清除
改進措施–新生代更新速度快,內存劃分比例不是1:1,按一大一小處理
3.標記整理算法:所有存活的對象往內存的一段移動,清理邊界的對象
4.(最常用)分代收集算法:根據對象生存週期不同將內存劃分爲新生代(大量對象死去,存活率低,複製算法),老生代(對象存活率高,標記清除),根據每塊區域的不同使用合適的算法

HotSpot(熱區)

可達性分析:
1.枚舉根節點(線程必須停頓,即使CMS收集器),逐一枚舉GC Roots節點引用鏈上節點必然不可能,大多數採用OopMap根據對象地址偏移量計算其位置或者JIT編譯過程中,將特定位置記錄放入棧/寄存器中
2.GC停頓:分析期間爲保證分析的準確性,引用不能變化,GC時必須暫停所有Java執行線程
3.OopMap系統停頓時,不需要檢查所有執行上下文及全文引用位置,而使用OoMap; 類加載完成時,HotSpot會記錄偏移量上的類型供GC掃描
4.SafePoint(安全點,在特定位置停住執行GC):程序並非在所有位置都停頓下來進行GC, 只有達到SafePoint時才停頓; SafePoint不能太少導致GC等待時間過長, SafePoint不能太多導致GC花費更多內存; 選取標準:是否讓程序長時間執行的特徵; 例如:方法跳轉,調用,異常跳轉導致產生SafePoint
5.搶斷式中斷:首先所有的線程中斷,若線程中斷的地方不再SafePoint上恢復,讓它跑到SafePoint上; 沒有虛擬機採用此做法
6.主動式中斷:GC是需要中斷線程,不直接對線程操作,而是設置一個標誌,各個線程主動輪詢這個標誌,發現標誌爲真,則自己中斷掛起//單個線程在標誌上不斷輪詢,爲真,掛起
7.SafeRegion(SafePoint的擴展):一段代碼片段中,引用關係不會發生變化,在這個區域中任意地方開始GC都是安全的//比如在線程sleep,blocked的狀態下,程序無法響應JVM中斷請求

垃圾回收器具體實現:

並行:多個垃圾回收器線程同時運行,但用戶線程暫停
併發:垃圾回收器和用戶線程同時運行,但在不同CPU上處理

  1. Serial收集器(標記整理算法):最初的收集器; 新生代收集器; 單線程收集器; Client模式下首選;//簡單高效沒有線程交互開銷

  2. ParNew收集器: Serial的多線程版; Server模式下首選,和CMS搭配

3.Parallel Scavenge收集器(複製算法): 新生代收集器; 並行多線程收集器;
用於達到可控的吞吐量, 而不是關注用戶縮短線程的暫停時間;意義在於高效率使用CPU,適合於後臺運算,不需要太多的交互任務
吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間); 精準控制吞吐量參數;
控制最大垃圾收集停頓時間,設置最小時間並不能使系統垃圾收集變快(設置時間:-XX:MaxGCPauseMillis;設置吞吐量:-XX:GCTimeRatio);
GC自動適應調整策略:根據當前系統運行情況收集性能監控信息,動態調整新生代大小,Eden與Survivor區比例大小,老年代對象年齡等參數,無需手工設置,提供最合適的停頓時間或者最大的吞吐量(-XX:+UseAdaptiveSizePolicy)
注:不能和CMS搭配

Serial Old收集器
1.Serial老年代版本
2.單線程收集器
3.使用標記整理算法
4.主要是Client端虛擬機使用
5.如果Server模式下,主要用於—JDK1.5以前配合Parallel Scavenge使用; 作爲CMS的後備預案,在併發收集發生Concurrent Mode Failure時使用

Parallel Old收集器
1.Parallel Scavenge收集器的老年代版本
2.多線程收集器
3.使用標記整理算法

CMS(Concurrent Mark Sweep//併發標記清理)大多數集中在B/S架構
1.獲取最短回收停頓時間爲目標
2.使用標記清理算法
3.運作過程如下—
–初始標記(CMS inital mark):標記GC Roots能直接關聯到的對象,速度快
–併發標記(CMS Concurrent Mark):進行GC Roots Tracing的過程
–重新標記(CMS remark):修正併發標記期間因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,比如初始化標記稍長,但遠比並發標記時間短
–併發清除(CMS Concurrent Sweep)
4.CMS收集器的內存回收過程是與用戶併發執行的
5.CMS明顯的缺點:
–CPU大於4個,CMS回收垃圾線程隨CPU數量的增加而減少; 當CPU數量少於4個, CMS對用戶程序的影響變得很大
–增量式併發收集器(Incremental Concurrent Mark Sweep/i-CMS)效果一般,不推薦使用
–無法處理浮動垃圾,Concurrent Mode Failure會導致另一次Full GC生產而只能將這部分回收失敗的垃圾留在下次GC再清理,因此CMS必須預留一部分空間提供給併發收集時的程序使用
6.JDK1.5默認設置老年代使用86%後激活CMS收集器,若老年代增長不是太快,可以提高參數(-XX:CMSInitiatingOccupancyFraction)觸發百分比; JDK1.6,CMS啓動閥值已經提高到92%
7.如果運行期間預留的內存不夠CMS,會出現Concurrent Mode Failure, 此時啓動後備預案:臨時啓動Serial Old收集器重新進行老年代垃圾收集,但會導致停頓時間很長;因此提高觸發百分比參數容易導致性能降低
8.標記算法容易產生很多內存碎片
9.CMS提供一個-XX:UseCMSCompactAtFullCollection開關參數,默認開啓,用於CMS收集器頂不住Full GC時,開啓內存碎片化整理過程,此過程無法併發,停頓時間較長
10.JVM提供-XX:CMSFullGCsBeforeCompaction,用於設置執行多少次不壓縮Full GC後進行一次壓縮的Full GC(默認爲0,表示每次進入Full GC時進行碎片化處理)

G1收集器(標記整理算法):
1.面向服務端應用
2.並行,併發
3.分代收集:將Java堆劃分爲多個大小相等獨立區域(Region),新生代和老生代依舊存在,屬於Region的集合
4.空間整理
5.可預測的停頓:
–使用Remembered Set來避免在整個Java堆中進行全區域的垃圾收集
–G1跟蹤收集各個垃圾堆積的價值大小(回收所獲得的空間及回收所需的時間),後臺維護一個優先級列表
–每次根據允許的收集時間,優先回收價值最大的Region(Carbage-First)
6.G1收集器運作大致可劃分爲一下步驟:
–初始化標記(Initial Marking)
–併發標記(Concurrent Marking)
–最終標記(Final Marking)
–篩選標記(Live Data Counting and Evacuation)

理解GC日誌:

例:33.125:[GC [DefNew: 3324K->152K(3712K),0.0025925 secs] 3324K->152K(11904K) 0.0031680 secs]
100.667:[Full GC [Tenured: 0K->210K(10240K),0.0149142 secs]4603K->210K(19456K),[Perm : 2999K->2999K(21248K)],0.0150007 secs][Times:user=0.01 sys=0.00,real=0.02 secs]
1.33.125:/100.667:表示GC發生時間
2."[GC,[Full GC":此次垃圾收集的停頓類型,不能用於區分新生代/老生代GC;Full表示發生停頓
3."[DefNew ,[Tenured ,[Perm":表示GC發生的區域,DefNew(收集器的新生代), ParNew(收集器老生代)
4.“3324K->152K(3712K)”:GC前內存使用量->GC後內存使用量(給內存區域總容量)
5."3324K->152K(11904K):GC前堆已使用量->GC後堆使用量(堆總容量)
6.“0.0031680secs”:內存區域GC所佔用的時間
7.[Times:user=0.01,sys=0.00,real=0.02secs]:指用戶,內核,操作開始到結束所花的時間

內存回收與分配策略

對象內存分配:
1.堆:大多數分配在堆上的Eden新生代區,TLAB//如果啓動本地線程分配緩衝,優先在TLAB上分配,Tenured//直接分配到老年代中
2.棧:JIT編譯
注:大對象直接進入老年代, 長期存活的對象進入老年代
動態對象年齡判斷:
1.當Survivor中的相同年齡的所有對象的大小總和大於Survivor的一半時,年齡大或者等於該年齡的對象可以直接進入老年代,不用等到MaxTenuredThresold設置的值

空間分配擔保:

1.在Minor GC之前,JVM會檢查老年代的連續內存空間夠不夠新生代所有對象的內存空間, 夠–Minor GC是安全的, 不夠–JVM會查看HandlePromotionFailure設置值是否允許擔保失敗:大於(嘗試進行Minor GC,儘管Minor GC是有風險的),小於(HandlePromotionFailure設置不允許冒險,會進行一次Full GC),允許(繼續檢查老年代的最大可用連續空間是否大於歷次晉升到老年代對象的平均水平)
"冒險"的概念:
1.大量的對象在Minor GC後仍然存活,需要老年代進行分配擔保
2.把Survivor無法容納的對象直接進入老年代
3.進行這樣的擔保,首先保證老年代空間足夠容納這些對象
4.對象多少能夠存活下來再實際完成內存回收之前無法確定,所以按照以前晉升老年代對象容量的品均值作爲參考
5.不夠,再確定是否需要進行Full GC來騰出更多的空間

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章