2. 垃圾收集器與內(nèi)存分配策略
2.1 判斷對象是否死亡的方法
2.1.1 引用計數(shù)法
引用計數(shù)算法(Reference Counting):給對象中添加一個引用計數(shù)器翠语,每當有一個地方引用它時艺挪,計數(shù)器值就加1狐蜕;當引用失效時,計數(shù)器值就減1;任何時刻計數(shù)器為0的對象就是已經(jīng)死去的對象。
它的缺點在于很難解決對象之間互相循環(huán)引用的問題。
2.1.2 可達性分析算法
可達性分析算法(Reachability Analysis):通過一系列的稱為“GC Roots”的對象作為起始點垄潮,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain)闷盔,當一個對象到GC Root沒有任何引用鏈相連時弯洗,則證明此對象是不可用的。
在Java語言中逢勾,可作為GC Roots的對象有:虛擬機棧中引用的對象牡整、方法區(qū)中類靜態(tài)屬性引用的對象、方法區(qū)中常量引用的對象溺拱、本地方法棧中引用的對象逃贝。
2.1.3 引用
引用分為強引用(Strong Reference)、軟引用(Soft Reference)迫摔、弱引用(Weak Reference)沐扳、虛引用(Phantom Reference)4種,這4種引用強度依次減弱句占。
- 強引用就是代碼中最普遍的沪摄。如“Object obj = new Object()”。
- 軟引用時用來描述一些還有用但并非必須的對象。只在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前才會回收杨拐。
- 弱引用也是用來描述非必需對象祈餐,程度較軟應用更低。無論內(nèi)存是否足夠戏阅,一旦垃圾收集器工作昼弟,弱引用的對象就會被回收。
- 虛引用也稱為幽靈引用或者幻影引用奕筐,它是最弱的一種引用關系。其唯一作用就是能在這個對象唄垃圾收集器回收時收到一個系統(tǒng)通知变骡。
2.1.4 求生之路
這個世界沒誰是非死不可的离赫,也沒有哪個對象是一定要被回收的。
在可達性分析算法中塌碌,要真正宣告一個對象死亡渊胸,至少要經(jīng)歷兩次標記過程:如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會被第一次標記并且進行一次篩選台妆,篩選的條件時此對象是否有必要執(zhí)行finalize()方法翎猛。當對象沒有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機調(diào)用過接剩,虛擬機將這兩種情況都視為沒有必要執(zhí)行切厘。有必要執(zhí)行finalize()方法的對象被放到F-Queue隊列中配對等待死亡的命運。
只要對象在finalize()方法中重新與引用鏈上的任何一個對象建立關聯(lián)就可成功自救懊缺。
(沒人救得了你疫稿,除了自己)
任何一個對象的finalize()方法只會被系統(tǒng)自動調(diào)用一次,如果對象面臨下一次回收鹃两,它的finalize()方法不會被再次執(zhí)行遗座。
(自救的機會只有一次,別妄想重來)
2.1.5 回收方法區(qū)
方法區(qū)一般不回收俊扳,并且它的回收效率遠低于新生代途蒋。
方法區(qū)的垃圾收集主要回收兩部分:廢棄常量和無用的類。
判定廢棄常量的方法:沒有其他地方引用了這個字面量馋记。
判定廢棄類的方法:
- 該類的所有實例都已經(jīng)被回收号坡,也就是Java堆中不存在該類的任何實例。
- 加載該類的ClassLoader已經(jīng)被回收抗果。
- 該類對應的java.lang.Class對象沒有在任何地方被引用筋帖,無法在任何地方通過反射訪問該類的方法。
2.2 垃圾收集算法
2.2.1 標記-清除算法
標記-清除算法(Mark-Sweep)算法分為標記和清除兩個階段:首先標記處所有需要回收的對象冤馏,在標記完成后統(tǒng)一回收所有被標記的對象日麸。
不足之處:1.效率不高,2.產(chǎn)生的空間碎片太多。
2.2.2 復制算法
復制算法(Copying)算法的出現(xiàn)是為了解決標記-清除算法的效率問題代箭。它將可用內(nèi)存按容量劃分為大小相等的兩塊墩划,每次只是用其中的一塊。當這一塊的內(nèi)存用完了嗡综,就將還存活的對象復制到另一塊乙帮,然后把已使用的內(nèi)存空間一次清理掉。代價是內(nèi)存減小一半极景。
有一種改進方案是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間察净,每次使用Eden和其中一塊Survivor。當回收時盼樟,將Eden和Survivor中還存活的對象一次性復制到另外一塊Survivor空間上氢卡,最后清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor空間的大小比例是8:1晨缴。
2.2.3 標記-整理算法
復制算法的缺點在于需要對存活率較高的對象進行較多次的復制操作译秦,效率較低。
標記-整理算法(Mark-Compact)的標記過程仍然與“標記-清除”算法一樣击碗,但后續(xù)的步驟不是直接對可回收對象進行清理筑悴,而是讓所有存活對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存稍途。
2.2.4 分代收集算法
當前商業(yè)虛擬機采用的就是分代收集算法(Generational Collection)阁吝,這種算法根據(jù)對象存活周期的不同將堆內(nèi)存劃分為幾塊,如新生代和老年代晰房。在新生代采用復制算法求摇,用老年代的空間對它進行分配擔保。而老年帶中因為對象存活率高殊者,沒有額外空間對它進行分配擔保与境,就必須使用“標記-清理”或者“標記-整理”算法進行回收。
2.3 垃圾收集器
2.3.1 Serial收集器
Serial收集器是最基本猖吴、發(fā)展歷史最悠久的收集器摔刁。這是一個單線線程收集器,但它大單線程的意義不僅僅說明它會使用一個CPU或一條收集線程去完成垃圾收集工作海蔽,更重要的是它在進行垃圾收集時共屈,必須暫停其他所有的工作線程,直到它收集結(jié)束党窜。
優(yōu)點是簡單高效拗引,缺點是用戶體驗極差。
2.3.2 ParNew收集器
ParNew收集器其實就是Serial收集器的多線程版本幌衣,除了使用多條線程收集垃圾之外矾削,其余行為與Serial一樣壤玫。
有點使得在垃圾回收時,其他用戶線程也能運行哼凯。
2.3.3 Paralle Scavenge收集器
Paralle Scavenge收集器是一個新生代收集器欲间,它也是采用復制算法的收集器。
其特點再與它的目標是達到一個可控制的吞吐量(Throughput)断部。所謂吞吐量就是CPU用于運行用戶代碼的時間與CPU總消耗時間的比值猎贴。由于與吞吐量關系密切,Parallel Scavenge收集器也經(jīng)常被稱為“吞吐量優(yōu)先”收集器蝴光。
2.3.4 Serial Old收集器
Serial Old是Serial收集器的老年帶版本她渴,它同樣是一個單線程收集器。
2.3.5 Parrallel Old收集器
Parrallel Old是Parrallel Scavenge收集器的老年代版本蔑祟,使用多線程和“標記-整理”算法惹骂。
2.3.6 CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以最短回收停頓時間為目標的收集器,是目前主流的收集器做瞪。該收集器旨在給用戶帶來較好的體驗,尤其重視服務的響應速度右冻,希望系統(tǒng)停頓時間最短装蓬。
CMS收集器是基于“標記-清除”算法實現(xiàn)的。過程分為4步:
1.初始標記
2.并發(fā)標記
3.重新標記
4.并發(fā)清除
CMS的優(yōu)點在于并發(fā)收集纱扭、低停頓牍帚。
缺點在于1.對CPU資源非常敏感,2.無法處理浮動垃圾(Floating Garbage)乳蛾,3.CMS是基于“標記-清除”算法實現(xiàn)暗赶,會產(chǎn)生大量空間碎片。
2.3.7 G1收集器
G1(Garbage-First)收集器的特點:
1.并行與并發(fā):充分利用多CPU的硬件優(yōu)勢縮短Stop-the-World停頓時間肃叶,通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行蹂随。
2.分代收集
3.空間整合:與CMS的“標記-清理”算法不同,G1從整體來看是基于“標記-整理”算法實現(xiàn)的收集器因惭,從局部來看是基于復制算法實現(xiàn)的岳锁。運行過程不會產(chǎn)生空間碎片,收集后能提供規(guī)整的可用內(nèi)存蹦魔。這種特性有利于程序長時間運行激率,分配大對象時不會因為無法找到連續(xù)的內(nèi)存空間而提前觸發(fā)下一次GC。
4.可預測的停頓
2.4 內(nèi)存分配與回收策略
2.4.1 對象優(yōu)先在Eden分配
大多數(shù)情況下勿决,對象在新生代Eden區(qū)中分配乒躺,當Eden區(qū)沒有足夠空間進行分配時,虛擬機將發(fā)起一次Minor GC(指發(fā)生在新生帶的垃圾回收動作低缩,因為Java對象大多具備朝生夕滅的特性嘉冒,所以Minor GC非常頻繁,一般回收速度也快)。
2.4.2 大對象直接進入老年代
多位的大對象是指需要大量連續(xù)內(nèi)存空間的Java對象健爬,典型的大對象就是那種很長的字符串以及數(shù)組控乾。
2.4.3 長期存活的對象將進入老年代
虛擬機給每個對象定義了一個對象年齡(Age)計數(shù)器。如果對象在Eden出生并經(jīng)過第一次Minor GC收仍然存活娜遵,并且能被Survivor容納的話蜕衡,將移到Survivor空間,并且設置對象年齡為1.對象在Survivor中每“熬過”一次Minor GC设拟,年齡就增加1歲慨仿,當年齡增加到一定程度(默認15歲),就會被晉升到老年代纳胧。
2.4.4 動態(tài)對象年齡判定
為了能更好地適應不同程序的內(nèi)存狀況镰吆,虛擬機并不是永遠地要求對象的年齡必須達到MaxTenurningThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半跑慕,年齡大于或等于該年齡的對象就可以直接進入老年代万皿,無須等到MaxTenurningThreshold要求的年齡。
2.4.5 空間分配擔保
在發(fā)生Minor GC之前核行,虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間牢硅,如果這個條件成立,那么Minor GC可以確保是安全的芝雪。如果不成立减余,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許惩系,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小位岔,如果大于,將嘗試著進行一次Minor GC堡牡,盡管這個Minor GC是有風險的抒抬;如果小于,或者HandlePromotionFailure設置不允許冒險悴侵,那這時也要改為進行一次Full GC瞧剖。
補充:常用的調(diào)優(yōu)參數(shù)
- -XX:SurvivorRatio:Eden和Survivor的比值,默認是8:1
- -XX:NewRatio:老年代和年輕帶內(nèi)存大小的比例
- -XX:MaxTenuringThreshold:對象從年輕帶晉升到老年代經(jīng)過GC次數(shù)的最大閾值