1.如何斷定對象已死java
判斷對象是否已死有兩種方法,一種是引用計數法,另外一種是可達性分析算法。算法
1.1引用計數法數據庫
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器就減一;任什麼時候刻爲0的對象就是不願再被使用的。數組
1.2.可達性分析緩存
經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路稱爲引用鏈,當一個對象到GC Roots沒有人格引用鏈項鍊,則證實對象是不可用的。在java語言中,能夠做爲GC Roots的對象包括下面幾種:多線程
1.2.1.虛擬機棧(棧幀中的本地變量表)中引用的對象。併發
1.2.2.方法區中靜態屬性引用的對象。框架
1.2.3.方法區中常量引用的對象。jvm
1.2.4.本地方法棧中JNI(即通常說的Native方法)引用的對象。 2.java中的四種引用佈局
2.1. 強引用:相似「Object o = new Object()」,只要強引用還存在,就不會被回收;
2.2.軟引用:用來描述一些有用但非必須的對象。若是一個對象只具備軟引用,則內存空間足夠, 垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒 有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高速緩存(若是內存夠,軟 引用沒有被回收,則能夠直接使用,若是內存不夠,軟引用已經被回收,則從新讀取數據(如從 數據庫中))。(java.lang.ref 包)SoftReferencesoftRef = new SoftReference(str);
2.3.弱引用:也是用來描述非必須對象的,可是它的強度比軟引用更弱一些,被弱引用關聯的對 象只能生存到下一次垃圾收集發生以前。當垃圾收集器工做時,不管當前內存是否足夠,都會回 收只被弱引用關聯的對象。若是這個對象是偶爾的使用,而且但願在使用時隨時就能獲取到,但 又不想影響此對象的垃圾收集,那麼你應該用 Weak Reference 來記住此對象。
2.4.虛引用:它是最弱的一種引用關係,一個對象是否有虛引用的存在,徹底不會對其生存時間 構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用的惟一目的就是能在 這個對象被收集器回收時收到一個系統通知。jdk1.2之後,提供了PhantomReference類來實現虛引用。 3.回收無效對象的過程
finalize()方法
即便在可達性分析算法中不可達的對象,也並不是是「非死不可」的,這時候它們暫時處於 「緩刑」階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程。標記的前提是對象在進 行可達性分析後發現沒有與 GC Roots 相鏈接的引用鏈。
3.1.第一次標記並進行一次篩選。
篩選的條件是此對象是否有必要執行 finalize()方法。當對象沒有覆蓋 finalize 方法,或者 finalize 方法已經被虛擬機調用過(finalize 只會調用一次),虛擬機將這兩種狀況都視爲「沒 有必要執行」,對象被回收。
3.2.第二次標記
若是這個對象被斷定爲有必要執行 finalize()方法,那麼這個對象將會被放置在一個 名爲:F-Queue 的隊列之中,並在稍後由一條虛擬機自動創建的、低優先級的 Finalizer 線 程去執行。這裏所謂的「執行」是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束。 這樣作的緣由是,若是一個對象 finalize()方法中執行緩慢,或者發生死循環(更極端的 狀況),將極可能會致使 F-Queue 隊列中的其餘對象永久處於等待狀態,甚至致使整個內存 回收系統崩潰。
finalize()方法是對象脫逃死亡命運的最後一次機會,稍後 GC 將對 F-Queue 中的對 象進行第二次小規模標記,若是對象要在 finalize()中成功拯救本身----只要從新與引用 鏈上的任何的一個對象創建關聯便可,譬如把本身賦值給某個類變量或對象的成員變量,那 在第二次標記時它將移除出「即將回收」的集合。若是對象這時候還沒逃脫,那基本上它就真 的被回收了。 4.回收方法區
方法區中主要清除兩種垃圾:
4.1. 廢棄常量
4.2. 無用的類
4.1.1.判斷廢棄的常量
清除廢棄的常量和清除對象相似,只要常量池中的常量不被任何變量或對象引用,那麼這些常量就會被清除掉。
4.2.1.如何判斷廢棄的類
清除廢棄類的條件較爲苛刻:
4.2.1. 該類的全部對象都已被清除。
4.2.2. 該類的java.lang.Class對象沒有被任何對象或變量引用。
只要一個類被虛擬機加載進方法區,那麼在堆中就會有一個表明該類的對象:java.lang.Class。這個對象在類被加載進方法區的時候建立,在方法區中該類被刪除時清除。
4.2.3. 加載該類的ClassLoader已經被回收。 5.垃圾收集算法
5.1.標記-清除算法(Mark-Sweep)。
首先標記處全部須要回收的對象,在標記完成後統一回 收。缺點:標記和清除兩個過程都效率低;標記清除後會產生大量不連續的內存碎片,空間 碎片太多可能會致使之後在程序運行中須要分配大對象時,沒法找到足夠的連續內存而不得 不提取觸發 GC。
5.2.複製算法。
將可用內存按容量劃分紅大小相等的兩塊,每次只使用一塊。當這一塊使用 完了,就將還存活着的對象複製到另外一塊上面,而後再把已使用過的內存一次清理掉。這樣 不用考慮內存碎片的問題,只要移動堆頂指針,按順序分配便可,實現簡單、運行高效。缺點:內存縮小爲原來的一半。現代商用虛擬機都採用這種算法回收新生代。而新生代中約 98%的對象都是「朝生夕死」,因此不需按 1:1 劃分。HotSpot 默認 Eden 和 Survivor 是 8:1,因此每次可用內存爲 90%。但 咱們無法保證每次回收只有很少於 10%的對象存活,當 Survivor 空間不夠時,須要依賴其餘 內存(這裏指老年代)進行分配擔保(直接進入老年代)。
缺點:若是對象存活率過高,要進行較多複製操做,效率低。且須要額外空間擔保,老 年代不能選用這種算法。
三、標記-整理算法。
過程與「標記-清除」同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存 活的對象都向一端移動,而後直接清理掉端邊界之外的內存。老年代由於對象存活率高、沒 有額外空間進行分配擔保,必須使用「標記-清理」或「標記-整理」算法。
將內存劃分爲老年代和新生代。老年代中存放壽命較長的對象,新生代中存放「朝生夕死」的對象。而後在不一樣的區域使用不一樣的垃圾收集算法。 6.JVM垃圾收集器
6.1.Serial 是一個單線程收集器,在它進行垃圾收集時,必須暫停其餘全部工做線程(StopTheWorld);簡單高效,是虛擬機在 Client模式下默認的新生代收集器(複製算法)。停頓 時間在幾十到一百多毫秒之內,能夠接受。
6.2.ParNew 其實就是 Serial 收集器的多線程版本;ParNew 收集器是許多運行在 Server模式下的虛擬機中首選的新生代收集器。除去性能因素,很重要的緣由是除了 Serial 收集 器外,目前只有它能與 CMS收集器(老年代)配合工做。(複製算法)
可是,在單 CPU環境中,ParNew收集器絕對不會有比 Serial 收集器更好的效果,甚至 因爲存在線程交互的開銷,該收集器在經過超線程技術實現的兩個 CPU的環境中都不能百分 之百地保證能夠超越 Serial收集器。然而,隨着可使用的 CPU的數量的增長,它對於 GC 時系統資源的有效利用仍是頗有好處的。
6.3.Parallel Scavenge 收集器是新生代垃圾收集器,使用複製算法,也是並行的多線程 收集器。與 ParNew 收集器相比,不少類似之處,可是 Parallel Scavenge 收集器更關注可控 制的吞吐量(運行用戶代碼時間/(運行用戶代碼+垃圾收集時間))。吞吐量越大,垃圾收集 的時間越短,則用戶代碼則能夠充分利用 CPU 資源,儘快完成程序的運算任務。
直觀上,只要最大的垃圾收集停頓時間越小,吞吐量是越高的,可是 GC 停頓時間的縮 短是以犧牲吞吐量和新生代空間做爲代價的。好比原來 10 秒收集一次,每次停頓 100 毫秒,如今變成 5 秒收集一次,每次停頓 70 毫秒。停頓時間降低的同時,吞吐量也降低了。
6.4.Serial Old 收集器是 Serial收集器的老年代版本,也是一個單線程收集器,採用「標記-整理算法」進行回收。其運行過程與 Serial收集器同樣。SerialOld收集器的主要意義也是在於給 Client 模式下的虛擬機使用。若是在 Server模式下,那麼它主要還有兩大用途:一種用途是在 JDK 1.5 以及以前的版本中與 Parallel Scavenge收集器搭配使用,另外一種用途就是做爲 CMS 收集器的後備預案,在併發收集發生 Concurrent Mode Failure時使用。
6.5.Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多線程和「標記-整理」算法進行垃圾回收。其一般與 Parallel Scavenge 收集器配合使用,「吞吐量優先」收集器 是這個組合的特色,在注重吞吐量和 CPU 資源敏感的場合,均可以使用這個組合。
6.6.CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器,基於「標記-清除」算法,從整體上來講,CMS收集器的內存回收過程是與用戶線程 一塊兒併發執行的(有的過程也是 StopTheWorld)。
CMS分爲四個步驟:初始標記(GCRoots能直接關聯到的對象,速度快,可達性分析, Stop The World),併發標記(可達性分析),從新標記(修正併發標記期間因用戶程序繼續 運做而致使的變更,速度快,Stop The World),併發清除
CMS 的優勢很明顯:併發收集、低停頓。因爲進行垃圾收集的時間主要耗在併發標記 與併發清除這兩個過程,雖然初始標記和從新標記仍然須要暫停用戶線程,可是從整體上看,
這部分佔用的時間相比其餘兩個步驟很小,因此能夠認爲是低停頓的。
缺點:
對 CPU 資源太敏感,這點能夠這麼理解,雖然在併發標記階段用戶線程沒有暫停,但 是因爲收集器佔用了一部分 CPU 資源,致使程序的響應速度變慢
CMS 收集器沒法處理浮動垃圾。所謂的「浮動垃圾」,就是在併發標記階段,因爲用戶程 序在運行,那麼天然就會有新的垃圾產生,這部分垃圾被標記事後,CMS 沒法在當次集中 處理它們(爲何?緣由在於 CMS 是以獲取最短停頓時間爲目標的,天然不可能在一次垃 圾處理過程當中花費太多時間),只好在下一次 GC 的時候處理。這部分未處理的垃圾就稱爲「浮 動垃圾」。因爲垃圾收集階段用戶線程還須要運行,那就不能等老年代幾乎全滿了再收集, 通常達到 92%時就開始收集,而 CMS 運行期間預留的內存沒法知足程序須要,就會出現 「Concurrent Mode Failure」,此時將啓動備用方案 serial old
因爲 CMS 收集器是基於「標記-清除」算法的(多是爲了時間短),前面說過這個算法會致使大量的空間碎片的產生,一旦空間碎片過多,大對象就沒辦法給其分配內存,那麼即 使內存還有剩餘空間容納這個大對象,可是卻沒有連續的足夠大的空間放下這個對象,因此 虛擬機就會觸發一次 Full GC。
在使用 CMS收集老年代時,新生代只能選用 ParNew或者 Serial 收集器中的一個(CMS 與其餘不配套,其餘的沒有使用傳統的 GC 收集器框架)
6.7.(Garbage-First)收集器,JDK1.7 纔開始商用。使用 G1 收集器時,Java 堆內存 佈局與其餘收集器有很大差異,它將整個 Java 堆分爲多個大小相等的獨立區域(Region), 雖然還保留新生代和老年代的概念,但新生代和老年代再也不是物理隔離的了,他們都是 Region(不須要連續)的集合。
特色:並行與併發。分代收集(不須要其餘收集器配合)。空間整合(總體來看採用「標 記-整理」,局部(兩個 Region 之間)採用複製)。可預測的停頓。
G1 跟蹤各個 Region 裏面的垃圾堆積價值大小(回收所得到的空間大小以及回收所需的 時間),在後臺維護一個優先列表,每次優先收集價值最大的 Region(因此叫 Garbage-First),
從而保證了 G1 在有限時間內能夠獲取儘量高的收集效率。
(老年代)過程:初始標記(StopTheWorld)、併發標記、最終標記(StopTheWorld)、篩選回收(Stop The World)
G1 的 YoungGC 就是將 E 區和 S 區複製到灰色的空白區。
G1 中有 Humongous 區(巨大區)用於存放比標準塊大 50%的對象
7.JVM垃圾回收機制
在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算 法,只須要付出少許存活對象的複製成本就能夠完成收集(有 eden和 survivor供複製,有 老年代最分配擔保)。而老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就 必須使用「標記-清理」或者「標記-整理」算法來進行回收。
發生 Minor GC,採用複製算法,發現
7.1.複製對象沒法所有放入 Survivor,只好經過分配擔保機制提早轉移到老年代中
7.2.大對象(長字符串或長數組等須要大量連續空間的對象)直接進入老年代(防止大
對象在 eden和 Survivor中常常複製)經過-XX:PretenureSizeThreshold 參數設置 (如 3MB),大於這個參數的直接進入老年代
7.3.長期存活對象進入老年代(默認 15歲)
Minor GC:新對象先放入 eden區,當 eden滿了會觸發 Minor GC。
Full GC(等於 Major GC):
7.3.一、每次進行 Minor GC 時,JVM 會計算 Survivor 區移至老年區的對象的平均大小,如 果這個值大於老年區的剩餘值大小則進行一次 Full GC
7.3.二、老年代空間不足時觸發 Full GC,只有在新生代對象轉入或建立爲大對象、大數組 時纔會出現不足的現象(大對象直接進入老年代),分配擔保
7.3.三、永久代滿(永久代 JDK8被移除)
優化 Full GC 自己不會先進行 Minor GC,咱們能夠配置,讓 Full GC 以前先進行一次 Minor GC,由於老年代不少對象都會引用到新生代的對象,先進行一次 Minor GC能夠提升老年代 GC 的速度。
在 jvm分帶垃圾回收機制中,將應用程序可用的堆空間分爲年輕代和老年代,又將年輕 代分爲 eden區、from區、to 區,新建對象老是在 eden 區中被建立,當 eden區空間已滿,
就觸發一次 Minor gc,將還被使用的對象複製到 from 區,這樣整個 eden 區都是未被使用 的空間,可供繼續建立對象,當 eden區再次用完,再觸發一次 Minor gc,將 eden 區和from 區還在被使用的對象複製到 to 區,下一次 Minor gc則是將 eden 區和 to區還被使用的對象 複製到 from區。所以,通過屢次 Minor gc,某些對象會在 from區和 to 區屢次複製,若是 超過某個閾值對象還未被釋放,則將對象複製到老年代。若是老年代空間也已用完,那麼就 會觸發 full gc,即所謂的全量回收。
永久代的垃圾回收主要有兩部分:廢棄常量和無用的類。如沒有任何 String對象引用 「abc」。在大量使用反射、動態代理、CGlib等 ByteCode框架,動態生成 JSP 以及 OSGi這 類頻繁自定義 ClassLoader 的場景都須要虛擬機具有類卸載功能(回收永久代),以保證永 久代不會溢出。