1.如何判定對象已死
判斷對象是否已死有兩種方法,一種是引用計數(shù)法,另一種是可達性分析算法。
1.1引用計數(shù)法
給對象中添加一個引用計數(shù)器,每當(dāng)有一個地方引用它時,計數(shù)器值就加1;當(dāng)引用失效時,計數(shù)器就減一;任何時刻為0的對象就是不肯再被使用的。
1.2.可達性分析
通過一系列的稱為“GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路稱為引用鏈,當(dāng)一個對象到GC Roots沒有人格引用鏈項鏈,則證明對象是不可用的。在java語言中,可以作為GC Roots的對象包括下面幾種:
1.2.1.虛擬機棧(棧幀中的本地變量表)中引用的對象。
1.2.2.方法區(qū)中靜態(tài)屬性引用的對象。
1.2.3.方法區(qū)中常量引用的對象。
1.2.4.本地方法棧中JNI(即一般說的Native方法)引用的對象。
2.java中的四種引用
2.1. 強引用:類似“Object o = new Object()”,只要強引用還存在,就不會被回收;
2.2.軟引用:用來描述一些有用但非必須的對象。如果一個對象只具有軟引用,則內(nèi)存空間足夠, 垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒 有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存(如果內(nèi)存夠,軟 引用沒有被回收,則可以直接使用,如果內(nèi)存不夠,軟引用已經(jīng)被回收,則重新讀取數(shù)據(jù)(如從 數(shù)據(jù)庫中))。(java.lang.ref 包)SoftReferencesoftRef = new SoftReference(str);
2.3.弱引用:也是用來描述非必須對象的,但是它的強度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對 象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時,無論當(dāng)前內(nèi)存是否足夠,都會回 收只被弱引用關(guān)聯(lián)的對象。如果這個對象是偶爾的使用,并且希望在使用時隨時就能獲取到,但 又不想影響此對象的垃圾收集,那么你應(yīng)該用 Weak Reference 來記住此對象。
2.4.虛引用:它是最弱的一種引用關(guān)系,一個對象是否有虛引用的存在,完全不會對其生存時間 構(gòu)成影響,也無法通過虛引用來取得一個對象實例。為一個對象設(shè)置虛引用的唯一目的就是能在 這個對象被收集器回收時收到一個系統(tǒng)通知。jdk1.2以后,提供了PhantomReference類來實現(xiàn)虛引用。
3.回收無效對象的過程
finalize()方法
即使在可達性分析算法中不可達的對象,也并非是“非死不可”的,這時候它們暫時處于 “緩刑”階段,要真正宣告一個對象死亡,至少要經(jīng)歷兩次標記過程。標記的前提是對象在進 行可達性分析后發(fā)現(xiàn)沒有與 GC Roots 相連接的引用鏈。
3.1.第一次標記并進行一次篩選。
篩選的條件是此對象是否有必要執(zhí)行 finalize()方法。當(dāng)對象沒有覆蓋 finalize 方法,或者 finalize 方法已經(jīng)被虛擬機調(diào)用過(finalize 只會調(diào)用一次),虛擬機將這兩種情況都視為“沒 有必要執(zhí)行”,對象被回收。
3.2.第二次標記
如果這個對象被判定為有必要執(zhí)行 finalize()方法,那么這個對象將會被放置在一個 名為:F-Queue 的隊列之中,并在稍后由一條虛擬機自動建立的、低優(yōu)先級的 Finalizer 線 程去執(zhí)行。這里所謂的“執(zhí)行”是指虛擬機會觸發(fā)這個方法,但并不承諾會等待它運行結(jié)束。 這樣做的原因是,如果一個對象 finalize()方法中執(zhí)行緩慢,或者發(fā)生死循環(huán)(更極端的 情況),將很可能會導(dǎo)致 F-Queue 隊列中的其他對象永久處于等待狀態(tài),甚至導(dǎo)致整個內(nèi)存 回收系統(tǒng)崩潰。
finalize()方法是對象脫逃死亡命運的最后一次機會,稍后 GC 將對 F-Queue 中的對 象進行第二次小規(guī)模標記,如果對象要在 finalize()中成功拯救自己----只要重新與引用 鏈上的任何的一個對象建立關(guān)聯(lián)即可,譬如把自己賦值給某個類變量或?qū)ο蟮某蓡T變量,那 在第二次標記時它將移除出“即將回收”的集合。如果對象這時候還沒逃脫,那基本上它就真 的被回收了。
4.回收方法區(qū)
方法區(qū)中主要清除兩種垃圾:
4.1. 廢棄常量
4.2. 無用的類
4.1.1.判斷廢棄的常量
清除廢棄的常量和清除對象類似,只要常量池中的常量不被任何變量或?qū)ο笠?,那么這些常量就會被清除掉。
4.2.1.如何判斷廢棄的類
清除廢棄類的條件較為苛刻:
4.2.1. 該類的所有對象都已被清除。
4.2.2. 該類的java.lang.Class對象沒有被任何對象或變量引用。
只要一個類被虛擬機加載進方法區(qū),那么在堆中就會有一個代表該類的對象:java.lang.Class。這個對象在類被加載進方法區(qū)的時候創(chuàng)建,在方法區(qū)中該類被刪除時清除。
4.2.3. 加載該類的ClassLoader已經(jīng)被回收。
5.垃圾收集算法
5.1.標記-清除算法(Mark-Sweep)。
首先標記處所有需要回收的對象,在標記完成后統(tǒng)一回 收。缺點:標記和清除兩個過程都效率低;標記清除后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間 碎片太多可能會導(dǎo)致以后在程序運行中需要分配大對象時,無法找到足夠的連續(xù)內(nèi)存而不得 不提取觸發(fā) GC。
5.2.復(fù)制算法。
將可用內(nèi)存按容量劃分成大小相等的兩塊,每次只使用一塊。當(dāng)這一塊使用 完了,就將還存活著的對象復(fù)制到另一塊上面,然后再把已使用過的內(nèi)存一次清理掉。這樣 不用考慮內(nèi)存碎片的問題,只要移動堆頂指針,按順序分配即可,實現(xiàn)簡單、運行高效。缺點:內(nèi)存縮小為原來的一半?,F(xiàn)代商用虛擬機都采用這種算法回收新生代。而新生代中約 98%的對象都是“朝生夕死”,所以不需按 1:1 劃分。HotSpot 默認 Eden 和 Survivor 是 8:1,所以每次可用內(nèi)存為 90%。但 我們沒法保證每次回收只有不多于 10%的對象存活,當(dāng) Survivor 空間不夠時,需要依賴其他 內(nèi)存(這里指老年代)進行分配擔(dān)保(直接進入老年代)。
缺點:如果對象存活率太高,要進行較多復(fù)制操作,效率低。且需要額外空間擔(dān)保,老 年代不能選用這種算法。
3、標記-整理算法。
過程與“標記-清除”一樣,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存 活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。老年代因為對象存活率高、沒 有額外空間進行分配擔(dān)保,必須使用“標記-清理”或“標記-整理”算法。
4. 分代收集算法
將內(nèi)存劃分為老年代和新生代。老年代中存放壽命較長的對象,新生代中存放“朝生夕死”的對象。然后在不同的區(qū)域使用不同的垃圾收集算法。
6.JVM垃圾收集器

6.1.Serial 是一個單線程收集器,在它進行垃圾收集時,必須暫停其他所有工作線程(StopTheWorld);簡單高效,是虛擬機在 Client模式下默認的新生代收集器(復(fù)制算法)。停頓 時間在幾十到一百多毫秒以內(nèi),可以接受。

6.2.ParNew 其實就是 Serial 收集器的多線程版本;ParNew 收集器是許多運行在 Server模式下的虛擬機中首選的新生代收集器。除去性能因素,很重要的原因是除了 Serial 收集 器外,目前只有它能與 CMS收集器(老年代)配合工作。(復(fù)制算法)
但是,在單 CPU環(huán)境中,ParNew收集器絕對不會有比 Serial 收集器更好的效果,甚至 由于存在線程交互的開銷,該收集器在通過超線程技術(shù)實現(xiàn)的兩個 CPU的環(huán)境中都不能百分 之百地保證可以超越 Serial收集器。然而,隨著可以使用的 CPU的數(shù)量的增加,它對于 GC 時系統(tǒng)資源的有效利用還是很有好處的。

6.3.Parallel Scavenge 收集器是新生代垃圾收集器,使用復(fù)制算法,也是并行的多線程 收集器。與 ParNew 收集器相比,很多相似之處,但是 Parallel Scavenge 收集器更關(guān)注可控 制的吞吐量(運行用戶代碼時間/(運行用戶代碼+垃圾收集時間))。吞吐量越大,垃圾收集 的時間越短,則用戶代碼則可以充分利用 CPU 資源,盡快完成程序的運算任務(wù)。
直觀上,只要最大的垃圾收集停頓時間越小,吞吐量是越高的,但是 GC 停頓時間的縮 短是以犧牲吞吐量和新生代空間作為代價的。比如原來 10 秒收集一次,每次停頓 100 毫秒,現(xiàn)在變成 5 秒收集一次,每次停頓 70 毫秒。停頓時間下降的同時,吞吐量也下降了。
6.4.Serial Old 收集器是 Serial收集器的老年代版本,也是一個單線程收集器,采用“標記-整理算法”進行回收。其運行過程與 Serial收集器一樣。SerialOld收集器的主要意義也是在于給 Client 模式下的虛擬機使用。如果在 Server模式下,那么它主要還有兩大用途:一種用途是在 JDK 1.5 以及之前的版本中與 Parallel Scavenge收集器搭配使用,另一種用途就是作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生 Concurrent Mode Failure時使用。
6.5.Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多線程和“標記-整理”算法進行垃圾回收。其通常與 Parallel Scavenge 收集器配合使用,“吞吐量優(yōu)先”收集器 是這個組合的特點,在注重吞吐量和 CPU 資源敏感的場合,都可以使用這個組合。
6.6.CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,基于“標記-清除”算法,從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程 一起并發(fā)執(zhí)行的(有的過程也是 StopTheWorld)。
CMS分為四個步驟:初始標記(GCRoots能直接關(guān)聯(lián)到的對象,速度快,可達性分析, Stop The World),并發(fā)標記(可達性分析),重新標記(修正并發(fā)標記期間因用戶程序繼續(xù) 運作而導(dǎo)致的變動,速度快,Stop The World),并發(fā)清除
CMS 的優(yōu)點很明顯:并發(fā)收集、低停頓。由于進行垃圾收集的時間主要耗在并發(fā)標記 與并發(fā)清除這兩個過程,雖然初始標記和重新標記仍然需要暫停用戶線程,但是從總體上看,
這部分占用的時間相比其他兩個步驟很小,所以可以認為是低停頓的。
缺點:
對 CPU 資源太敏感,這點可以這么理解,雖然在并發(fā)標記階段用戶線程沒有暫停,但 是由于收集器占用了一部分 CPU 資源,導(dǎo)致程序的響應(yīng)速度變慢
CMS 收集器無法處理浮動垃圾。所謂的“浮動垃圾”,就是在并發(fā)標記階段,由于用戶程 序在運行,那么自然就會有新的垃圾產(chǎn)生,這部分垃圾被標記過后,CMS 無法在當(dāng)次集中 處理它們(為什么?原因在于 CMS 是以獲取最短停頓時間為目標的,自然不可能在一次垃 圾處理過程中花費太多時間),只好在下一次 GC 的時候處理。這部分未處理的垃圾就稱為“浮 動垃圾”。由于垃圾收集階段用戶線程還需要運行,那就不能等老年代幾乎全滿了再收集, 一般達到 92%時就開始收集,而 CMS 運行期間預(yù)留的內(nèi)存無法滿足程序需要,就會出現(xiàn) “Concurrent Mode Failure”,此時將啟動備用方案 serial old
由于 CMS 收集器是基于“標記-清除”算法的(可能是為了時間短),前面說過這個算法會導(dǎo)致大量的空間碎片的產(chǎn)生,一旦空間碎片過多,大對象就沒辦法給其分配內(nèi)存,那么即 使內(nèi)存還有剩余空間容納這個大對象,但是卻沒有連續(xù)的足夠大的空間放下這個對象,所以 虛擬機就會觸發(fā)一次 Full GC。

在使用 CMS收集老年代時,新生代只能選用 ParNew或者 Serial 收集器中的一個(CMS 與其他不配套,其他的沒有使用傳統(tǒng)的 GC 收集器框架)
6.7.(Garbage-First)收集器,JDK1.7 才開始商用。使用 G1 收集器時,Java 堆內(nèi)存 布局與其他收集器有很大差別,它將整個 Java 堆分為多個大小相等的獨立區(qū)域(Region), 雖然還保留新生代和老年代的概念,但新生代和老年代不再是物理隔離的了,他們都是 Region(不需要連續(xù))的集合。
特點:并行與并發(fā)。分代收集(不需要其他收集器配合)??臻g整合(整體來看采用“標 記-整理”,局部(兩個 Region 之間)采用復(fù)制)。可預(yù)測的停頓。
G1 跟蹤各個 Region 里面的垃圾堆積價值大?。ɑ厥账@得的空間大小以及回收所需的 時間),在后臺維護一個優(yōu)先列表,每次優(yōu)先收集價值最大的 Region(所以叫 Garbage-First),
從而保證了 G1 在有限時間內(nèi)可以獲取盡可能高的收集效率。
(老年代)過程:初始標記(StopTheWorld)、并發(fā)標記、最終標記(StopTheWorld)、篩選回收(Stop The World)
G1 的 YoungGC 就是將 E 區(qū)和 S 區(qū)復(fù)制到灰色的空白區(qū)。
G1 中有 Humongous 區(qū)(巨大區(qū))用于存放比標準塊大 50%的對象


7.JVM垃圾回收機制
在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,只有少量存活,那就選用復(fù)制算 法,只需要付出少量存活對象的復(fù)制成本就可以完成收集(有 eden和 survivor供復(fù)制,有 老年代最分配擔(dān)保)。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔(dān)保,就 必須使用“標記-清理”或者“標記-整理”算法來進行回收。
發(fā)生 Minor GC,采用復(fù)制算法,發(fā)現(xiàn)
7.1.復(fù)制對象無法全部放入 Survivor,只好通過分配擔(dān)保機制提前轉(zhuǎn)移到老年代中
7.2.大對象(長字符串或長數(shù)組等需要大量連續(xù)空間的對象)直接進入老年代(防止大
對象在 eden和 Survivor中經(jīng)常復(fù)制)通過-XX:PretenureSizeThreshold 參數(shù)設(shè)置 (如 3MB),大于這個參數(shù)的直接進入老年代
7.3.長期存活對象進入老年代(默認 15歲)
Minor GC:新對象先放入 eden區(qū),當(dāng) eden滿了會觸發(fā) Minor GC。
Full GC(等于 Major GC):
7.3.1、每次進行 Minor GC 時,JVM 會計算 Survivor 區(qū)移至老年區(qū)的對象的平均大小,如 果這個值大于老年區(qū)的剩余值大小則進行一次 Full GC
7.3.2、老年代空間不足時觸發(fā) Full GC,只有在新生代對象轉(zhuǎn)入或創(chuàng)建為大對象、大數(shù)組 時才會出現(xiàn)不足的現(xiàn)象(大對象直接進入老年代),分配擔(dān)保
7.3.3、永久代滿(永久代 JDK8被移除)
優(yōu)化 Full GC 本身不會先進行 Minor GC,我們可以配置,讓 Full GC 之前先進行一次 Minor GC,因為老年代很多對象都會引用到新生代的對象,先進行一次 Minor GC可以提高老年代 GC 的速度。
在 jvm分帶垃圾回收機制中,將應(yīng)用程序可用的堆空間分為年輕代和老年代,又將年輕 代分為 eden區(qū)、from區(qū)、to 區(qū),新建對象總是在 eden 區(qū)中被創(chuàng)建,當(dāng) eden區(qū)空間已滿,
就觸發(fā)一次 Minor gc,將還被使用的對象復(fù)制到 from 區(qū),這樣整個 eden 區(qū)都是未被使用 的空間,可供繼續(xù)創(chuàng)建對象,當(dāng) eden區(qū)再次用完,再觸發(fā)一次 Minor gc,將 eden 區(qū)和from 區(qū)還在被使用的對象復(fù)制到 to 區(qū),下一次 Minor gc則是將 eden 區(qū)和 to區(qū)還被使用的對象 復(fù)制到 from區(qū)。因此,經(jīng)過多次 Minor gc,某些對象會在 from區(qū)和 to 區(qū)多次復(fù)制,如果 超過某個閾值對象還未被釋放,則將對象復(fù)制到老年代。如果老年代空間也已用完,那么就 會觸發(fā) full gc,即所謂的全量回收。
永久代的垃圾回收主要有兩部分:廢棄常量和無用的類。如沒有任何 String對象引用 “abc”。在大量使用反射、動態(tài)代理、CGlib等 ByteCode框架,動態(tài)生成 JSP 以及 OSGi這 類頻繁自定義 ClassLoader 的場景都需要虛擬機具備類卸載功能(回收永久代),以保證永 久代不會溢出。