引用
- 狹義引用
- 地址
- 擴充引用
- 強引用 Strong Reference
- Object obj = new Object()
- 軟引用 Soft Reference
- SoftReference稽犁,將要發(fā)生內(nèi)存溢出才會回收
- 弱引用 Weak Reference
- WeakReference附较,不影響回收嘹叫,可做回收通知
- 虛引用 Phantom Reference
- PhantomReference醇份,不影響回收,可做回收通知
- 強引用 Strong Reference
對象判活
- 引用計數(shù)法
- 無法解決循環(huán)引用
- 可達性分析
- GC Roots
- 虛擬機棧中引用
- 類靜態(tài)屬性引用
- 方法區(qū)常量引用
- 本地方法棧中JNI引用
- 對象狀態(tài)
- GC Roots可達
- GC Roots不可達但需執(zhí)行finalize
- 不可達也不需執(zhí)行finalize
- 執(zhí)行finalize
- 進入F-Queue姥敛,由虛擬機建立的低優(yōu)先級Finalizer線來執(zhí)行励七,此執(zhí)行表示觸發(fā)崇裁,但不保證執(zhí)行結(jié)束
- 若finalize方法卡死,會被咔嚓掉
- finalize方法中可進行一次自救蜻底,但只能救一次
- finalize方法據(jù)說是在初期對應(yīng)C/C++的析構(gòu)函數(shù)而做的一次折中骄崩,實際并不提倡使用,而用try-finally更好
- 枚舉根節(jié)點
- 必須Stop The World薄辅,所以對效率很敏感要拂,全局掃描是不現(xiàn)實的
- 得益于準確式GC,虛擬機知道某個地址存的是什么類型數(shù)據(jù)站楚,以此建立OopMap(Ordinary Object Pointer)
- 虛擬機沒有為每條指令都建立OopMap脱惰,因為成本太高
- 所以有了Safe Point的概念,“可長時間執(zhí)行”的地方窿春,會建立OopMap拉一,即安全點
- 可長時間執(zhí)行一般指指令序列復(fù)用采盒,即方法調(diào)用,循環(huán)跳轉(zhuǎn)蔚润,異常跳轉(zhuǎn)
- 有安全點后磅氨,需要考慮怎么讓線程都停在安全點
- 搶先式中斷(Preemptive Suspension)
- 發(fā)生GC時虛擬機停止所有線程,恢復(fù)沒有跑到安全點的線程嫡纠,等其安全烦租,幾乎不用
- 主動式中斷(Voluntary Suspension)
- 生成一個test輪詢指令,在安全點處執(zhí)行除盏,看是否有中斷標識存在叉橱,若存在就停下自己
- 具體方案是虛擬機將一個內(nèi)存頁設(shè)為不可讀,test讀取到這里時會產(chǎn)生自陷異常信號者蠕,并在異常處理器中進行線程中斷
- 安全點的問題
- 線程處于sleep或者block時赏迟,等線程跑到安全點不現(xiàn)實
- 安全區(qū)域(Safe Region)
- 線程進入安全區(qū)域時,就在自己身上打個安全區(qū)域的標簽蠢棱,GC枚舉根節(jié)點時锌杀,就不用管了
- 當(dāng)線程將要離開安全區(qū)域時,需要檢查系統(tǒng)是否已經(jīng)完成根節(jié)點枚舉或者GC
- 如果沒有泻仙,就等待直到收到可以離開的信號為止
- GC Roots
方法區(qū)回收
- 虛擬機規(guī)范并未要求方法區(qū)回收
- 回收對象
- 廢棄常量
- 不被引用糕再,會被清出常量池
- 無用類
- 該類所有實例已回收
- 類加載器已被回收
- Class對象沒有在任何地方被引用
- 僅僅是可回收,不一定就回收玉转,HotSpot提供了參數(shù)可控制
- 廢棄常量
收集算法
- 復(fù)制(對新生代分區(qū)后突想,涉及到分配擔(dān)保Handle Promotion,需要老年代做擔(dān)保)
- 標記-清除(弊端是空間碎片)
- 標記-整理
- 實際
- 分代收集
收集器
- 搭配關(guān)系
- 年輕代 - 年老代
- Serial(Serial Old究抓,CMS)
- ParNew(Serial Old猾担,CMS)
- Parallel Scavenge(Serial Old(實際應(yīng)該是PS MarkSweep,只是它和Serial Old的實現(xiàn)太接近了刺下,以至于很多資料中直接以Serial Old來代替)绑嘹,Parallel Old)
- 年老代 - 年輕代
- CMS(Serial,ParNew)
- Serial Old(Serial橘茉,ParNew工腋,Parallel Scavenge)
- Parallel Old(Parallel Scavenge)
- G1
- 年輕代 - 年老代
- 說明
- Parallel Scavenge和G1采用全新代碼框架,其余則共用架構(gòu)
- 并行:GC多線程畅卓,用戶線程需停止
- 并發(fā):用戶線程與GC線程同時執(zhí)行擅腰,但可能是交替占用cpu,只是用戶線程不需要暫停
- 新生代
- Serial(復(fù)制算法)
- 用戶多線程-》GC單線程-》用戶多線程
- 單Cpu下無敵翁潘,甚至2 Cpu下還能虐ParNew
- ParNew(復(fù)制算法)
- 用戶多線程-》GC多線程-》用戶多線程
- 除多線程外趁冈,與Serial基本一致,存在的一個主要原因是只有它能和CMS一起用拜马,而Parallel Scavenge不行
- Parallel Scavenge(復(fù)制)
- 和ParNew有相似之處渗勘,不過更關(guān)注于可控的吞吐量
- MaxGCPauseMillis GC最大停頓時間
- GCTimeRatio 吞吐量
- UseAdapativeSizePolicy 自適應(yīng)調(diào)節(jié)新生代大小矾飞,Eden比例,晉升老年代對象年等
- Serial(復(fù)制算法)
- 老年代
- Serial Old(標記-整理)
- 用戶多線程-》GC單線程-》用戶多線程
- 可作為CMS發(fā)生Concurrent Mode Failure時的備用方案
- Parallel Old(標記-整理)
- 用戶多線程-》GC多線程-》用戶多線程
- 在這個出來前呀邢,由于Parallel Scavenge只能和Serial Old一起使用洒沦,所以實際在吞吐量控制中,效果可能并不如ParNew + CMS
- CMS(Concurrent Mark Sweep价淌,標記-清除)
- 用戶多線程-》初始標記單線程-》用戶多線程(并發(fā)標記)-》重新標記多線程-》用戶多線程(并發(fā)清理)-》用戶多線程(重置線程)
- 主打短停頓
- 初始標記(Initial Mark)
- 標記GC Roots能直接關(guān)聯(lián)的對象申眼;單線程
- 并發(fā)標記(Concurrent Mark)
- GC Roots Tracing
- 重新標記(Remark)
- 修正并發(fā)標記期間的改變,比初始標記稍長蝉衣,遠短于并發(fā)標記括尸;多線程
- 并發(fā)清除(Concurrent Sweep)
- 初始標記 + 重新標記
- Stop The World
- CMS缺點
- 并發(fā)GC會消耗cpu,對用戶線程造成影響
- 為此病毡,曾經(jīng)出現(xiàn)過Incremental Concurrent Mark Sweep濒翻,思想是讓GC和用戶線程交替運行,減少對cpu的占用
- 效果并不理想啦膜,已經(jīng)被deprecate
- 無法處理浮動垃圾
- 浮動垃圾是指在并發(fā)清除階段產(chǎn)生的垃圾有送,本次GC無法清除
- Concurrent Mode Failure
- 由于并發(fā)清除階段需要運行用戶線程,所以需要預(yù)留空間
- 預(yù)留空間太大容易導(dǎo)致GC頻繁
- 通過-XX:CMSInitiatingOccupancyFraction調(diào)高的話僧家,又可能導(dǎo)致Concurrent Mode Failure
- 發(fā)生Failure時雀摘,會啟用Serial Old重新進行老年代收集
- 空間碎片
- 提供-XX:+UseCMSCompactAtFullCollection 默認開啟
- -XX:CMSFullGCsBeforeCompaction 默認值0
- 并發(fā)GC會消耗cpu,對用戶線程造成影響
- Serial Old(標記-整理)
- G1
- 1.7中出現(xiàn),可處理整個堆
- 標記-整理
- 用戶多線程-》初始標記單線程-》用戶多線程(并發(fā)標記)-》最終標記多線程-》篩選回收多線程-》用戶多線程
- 優(yōu)勢
- 可預(yù)測的停頓八拱,用戶可以控制M毫秒內(nèi)阵赠,GC所用時間不能超過N毫秒
- 基于Region,即將整個Java堆劃分為若干等大小的Region肌稻,新生代和老年代不再物理隔離清蚀,而都是一部分Region的集合
- G1跟蹤各個Region垃圾堆積的價值大小(回收所獲得空間大小和回收時間的經(jīng)驗值)爹谭,在后臺維護一個優(yōu)先列表枷邪,每次根據(jù)允許的時間,收集價值最大的Region旦棉,這也是Garbage-First的名稱由來
- 基于Region有個突出的問題(這個問題在新生代/老年代中也存在齿风,只是沒有這么突出),因為對象引用關(guān)系可能是跨Region的绑洛,如果沒有對應(yīng)措施的話,會導(dǎo)致全堆掃描童本,這是不能容忍的
- 虛擬機采用的方案是Remembered Set
- 每個Region都有一個對應(yīng)的Remembered Set
- 虛擬機發(fā)現(xiàn)應(yīng)用程序在對Reference的數(shù)據(jù)進行寫操作時真屯,會產(chǎn)生一個Write Barrier暫時中斷寫操作
- 檢查Reference所引用的對象是否在不同的Region
- 若是,則通過Card Table在被引用對象所屬Region的Remembered Set中記錄該信息
- 枚舉根節(jié)點時穷娱,掃描Remembered Set以確保掃描不遺漏
- 步驟(暫不考慮維護Remembered Set的工作)
- 初始標記(Initial Marking)
- 并發(fā)標記(Concurrent Marking)
- 將對象變化記錄在Remembered Set Logs
- 最終標記(Final Marking)
- 將Remembered Set Logs合并到Remembered Set中
- 篩選回收(Live Data Counting and Evacuation)
- 先對Region的回收價值進行排序绑蔫,然后根據(jù)用戶的期望時間制定回收計劃运沦,這部分其實可以做到并發(fā),只是不并發(fā)的效率更高配深,所以實際實現(xiàn)時并不是并發(fā)
G1和CMS對比
- 性能對比
- 軟實時目標(M時間中最大允許GC為N)
- G1的失敗概率小于CMS携添,并且失敗情況下,G1的超時也小于CMS篓叶;此對比下烈掠,G1完勝
- 吞吐量對比
- CMS占優(yōu)勢
- 軟實時目標(M時間中最大允許GC為N)
- 共同點:
- 都立足于低停頓(各種并發(fā)就看出來了)
- 選擇
- 在1.7時,建議的還是CMS
- 不過呢缸托,G1在低停頓已經(jīng)有優(yōu)勢左敌,只是吞吐量還不太好
內(nèi)存分配策略
-
TLAB:Thread Local Allocation Buffer
- 在線程中劃出緩沖區(qū),用于對象新建
- 為什么要這個東西呢俐镐,減少分配競爭
對象優(yōu)先在Eden中分配
大對象直接進入老年代矫限,虛擬機提供參數(shù)-XX:PretenureSizeThreshold來控制,大于這個size的對象直接進入老年代佩抹,不過這參數(shù)只對Serial和ParNew有效
長期存活對象進入老年代叼风,為每個對象賦予一個年齡值,達到一定年齡后進入老年代棍苹,可通過參數(shù)-XX:MaxTenuringThreshold來設(shè)置
動態(tài)對象年齡判定咬扇,Survivor中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代廊勃,而無需等到-XX:MaxTenuringThreshold
空間分配擔(dān)保:在發(fā)生Minor GC之前懈贺,虛擬機會先檢查老年代中連續(xù)空間是否大于新生代所有對象空間總和,如果條件成立坡垫,那么可以確保是安全的梭灿;如果不成立,則會查看HandlePromotionFailures設(shè)置值是否允許擔(dān)保失敱啤堡妒;如果允許,那么繼續(xù)檢查老年代可用連續(xù)空間是否大于歷次晉升老年代的平均容量大小溉卓,如果大于皮迟,則嘗試Minor GC;否則桑寨,或者HandlePromotionFailures設(shè)置不允許冒險伏尼,則進行Full GC
不過在JDK6 Update24之后,HandlePromotionFailures已經(jīng)被拋棄了尉尾,新規(guī)則變?yōu)槔夏甏B續(xù)空間大于新生代對象總大小爆阶,或者大于歷次晉升平均大小,就進行Minor GC;否則辨图,進行Full GC
擔(dān)保失敗時班套,會重新觸發(fā)Full GC;雖然這比直接Full GC代價要大故河,但是從總體減少Full GC頻率上吱韭,還是很有效的