垃圾回收:java
將已經分配出去的內存,取再也不使用的回收回來,已便於再次分配算法
如何區分對象是否變成垃圾?安全
引用計數法與可達性分析多線程
引用計數法:併發
使用計數器統計對象引用個數jvm
使用語言:性能
Python線程
問題:指針
須要額外的空間存儲計數器,頻繁更新操做對象
沒法處理循環引用問題
可達性分析:
GCRoot做爲初始存活對象合集
GCRoot:
堆外指向堆內的引用
GCRoot範圍:
Java方法棧幀中的局部變量
已加載類的靜態變量
JNI handles
已啓動且未中止的Java線程
好處:
解決了循環引用問題
問題:
多線程狀況下可能出現漏報和誤報
Stop-the-world以及安全點
解決方案:
STW(Stop the World),中止其餘非垃圾回收的工做,直到垃圾回收完成,這一段時間會形成GC暫停。
經過安全點機制來實現STW,當全部線程都到達安全點,才准許STW獨佔線程工做。
安全點的做用,找到一個穩定狀態,jvm不在產生堆棧。
垃圾回收的三種方式
標記-清除:
原理及其簡單,形成內存碎片,分配效率低
標記-壓縮:
解決內存碎片化問題,代價壓縮算法性能開銷
標記-複製:
一樣能解決內存碎片化問題,缺點堆空間使用效率低下
JVM虛擬機的堆劃分
JVM經過分代思想進行垃圾回收,JVM堆分爲新生代和老年代。
新生代:
Eden區以及兩個大小相同的Survivor區
Eden存儲對象的內存,對空間是線程共享,所以劃分空間是須要同步。
經過TLAB避免兩個對象共用一段內存。
TLAB能夠向JVM申請一段連續的內存爲線程私有。
申請操做須要加鎖Eden區內存耗盡怎麼辦?
JVM便會觸發Minor GC,存活對象放置到Survivor區。
from指針的survivor區中的對象會複製到to指向的Survivor區。
交換from和to指針,下次Minor GC時,to指向的survivor區仍是空的。
若是JVM對象在survivor區複製默認超過15次晉升到老年代,或者單個survivor區已佔用50%,較高複製次數的對象也會晉升到老年代。
MinorGC好處不會對整個堆進行垃圾回收。
可是有一個問題,老年代的對象可能引用新生代的對象。
在標記存活對象時,咱們須要掃描老年代的對象。
若是該對象擁有對新生代對象的引用,這個也會被做爲GCRoots。
可能會致使從新作一次全堆掃描,引入了卡表技術
卡表
卡表(Card Table):
將整個堆分爲一個個大小爲512字節的卡,而且維護一個卡表,用來存儲每張卡的一個標識位。
這個標識位表明對象的卡是否可能存在指向新生代對象的引用。若是存在,咱們認爲這張卡是髒卡。
如何設置卡的標識位?
JVM獲取每一個引用型實例的寫操做,並記錄寫標識位的操做
解釋執行實現簡單。JIT則須要插入額外邏輯,寫屏障(write barrier)。
寫屏障:
能加大Minor GC的效率,可是會帶來虛共享問題,能夠經過-XX:UseCondCardMark,來儘可能減小寫卡表的操做
垃圾回收器類型
Serial:
最古老、最穩定的垃圾回收器
可同時做用於新生代和老年代
新生代採用複製算法
老年代採用標記-壓縮算法
ParNew:
Serial收集器新生代的並行版本
複製算法
多線程,需多核支持
-XX:ParallelGCThreads限制線程數量
Parallel收集器:
相似ParNew,
新生代複製算法,老年代標記壓縮
更加關注吞吐量
-XX:+UseParallelGC使用Parallel收集器+老年代串行
-XX:+UseParallelOldGC使用Parallel收集器+老年代並行
-XX:+MaxGCPauseMills最大停頓時間,單位毫秒,GC儘可能保證回收不超過該時間
-XX:+GCTimeRatio 0-100的取值範圍,垃圾收集時間佔總時間的比,默認99,即最大容許1%時間作GC
TIPS:停頓時間和吞吐量不可能同時調優
CMS收集器:
Concurrent Mark Sweep併發標記清除
標記清除算法
併發階段下降吞吐量
老年代收集器(新生代使用ParNew)
-XX:+UseConcMarkSweepGC
G1收集器:
橫跨新生代和老年代
打亂堆結構,堆分紅不少個區域,每一個區域均可以充當Eden區、Survivor區或者老年代其中一個
標記壓縮算法
G1在Java7中發佈取代CMS, java9之後CMS收集器廢棄
優先回收死亡對象較多的區域
ZGC收集器:
暫停時間不超過10ms
java11將提供