JVM優(yōu)化:垃圾回收概述
一、什么是垃圾回收
說起垃圾收集(Garbage Collection, 下文簡稱GC) , 有不少人把這項技術(shù)當作Java語言的伴生產(chǎn)物。 事實上, 垃圾收集的歷史遠遠比Java久遠, 在1960年誕生于麻省理工學(xué)院的Lisp是第一門開始使 用內(nèi)存動態(tài)分配和垃圾收集 技術(shù)的語言。垃圾收集需要完成的三件事情: 哪些內(nèi)存需要回收? 什么時候回收? 如何回收?
二、Java垃圾回收的優(yōu)缺點
優(yōu)點:
a.不需要考慮內(nèi)存管理,
b.可以有效的防止內(nèi)存泄漏,有效的利用可使用的內(nèi)存,
c.由于有垃圾回收機制,Java中的對象不再有"作用域"的概念,只有對象的引用才有"作用域"
缺點:
java開發(fā)人員不了解自動內(nèi)存管理, 內(nèi)存管理就像一個黑匣子,過度依賴就會降低我們解決內(nèi)存溢出/內(nèi)存泄漏等問題 的能力。
三、判斷對象是否存活 - 引用計數(shù)算法
引用計數(shù)算法可以這樣實現(xiàn):給每個創(chuàng)建的對象添加一個引用計數(shù)器,每當此對象被某個地方引用時,計數(shù)值+1, 引用失效時-1,所以當計數(shù)值為0時表示對象已經(jīng)不能被使用。引用計數(shù)算法大多數(shù)情況下是個比較不錯的算法, 簡單直接,也有一些著名的應(yīng)用案例但是對于Java虛擬機來說,并不是一個好的選擇,因為它很難解決對象直接相 互循環(huán)引用的問題。
優(yōu)點: 實現(xiàn)簡單,執(zhí)行效率高,很好的和程序交織。
缺點: 無法檢測出循環(huán)引用。
譬如有A和B兩個對象,他們都互相引用,除此之外都沒有任何對外的引用,那么理論上A和B都可以被作為垃 圾回收掉,但實際如果采用引用計數(shù)算法,則A、B的引用計數(shù)都是1,并不滿足被回收的條件,如果A和B之 間的引用一直存在,那么就永遠無法被回收了
四、判斷對象是否存活-可達性分析算法
在主流的商用程序語言如Java、C#等的主流實現(xiàn)中,都是通過可達性分析(Reachability Analysis)來判斷對象是否存 活的。此算法的基本思路就是通過一系列的“GC Roots”的對象作為起始點,從起始點開始向下搜索到對象的路徑。 搜索所經(jīng)過的路徑稱為引用鏈(Reference Chain),當一個對象到任何GC Roots都沒有引用鏈時,則表明對象“不可 達”,即該對象是不可用的。
在Java語言中,可作為GC Roots的對象包括下面幾種:
- 棧幀中的局部變量表中的reference引用所引用的對象
- 方法區(qū)中static靜態(tài)引用的對象
- 方法區(qū)中final常量引用的對象
- 本地方法棧中JNI(Native方法)引用的對象
- Java虛擬機內(nèi)部的引用, 如基本數(shù)據(jù)類型對應(yīng)的Class對象, 一些常駐的異常對象(比如 NullPointExcepiton、 OutOfMemoryError) 等, 還有系統(tǒng)類加載器。
- 所有被同步鎖(synchronized關(guān)鍵字) 持有的對象。
- 反映Java虛擬機內(nèi)部情況的JMXBean、 JVMTI中注冊的回調(diào)、 本地代碼緩存等。
五、JVM之判斷對象是否存活
finalize()方法最終判定對象是否存活:
即使在可達性分析算法中判定為不可達的對象, 也不是“非死不可”的, 這時候它們暫時還處于“緩 刑”階段, 要真 正宣告一個對象死亡, 至少要經(jīng)歷兩次標記過程:
第一次標記:
如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈, 那它將會被第一次標記, 隨后進行一次篩 選, 篩選的條件是此對象是否有必要執(zhí)行finalize()方法。
沒有必要:
假如對象沒有覆蓋finalize()方法, 或者finalize()方法已經(jīng)被虛擬機調(diào)用過, 那么虛擬機將這兩種情況都視為“沒有必 要執(zhí)行”。
有必要:
如果這個對象被判定為確有必要執(zhí)行finalize()方法, 那么該對象將會被放置在一個名為F-Queue的 隊列之中, 并在 稍后由一條由虛擬機自動建立的、 低調(diào)度優(yōu)先級的Finalizer線程去執(zhí)行它們的finalize() 方法。 finalize()方法是對 象 逃脫死亡命運的最后一次機會, 稍后收集器將對F-Queue中的對象進行第二次小規(guī)模的標記, 如果對 象要在 finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關(guān)聯(lián)即可, 譬如把自己 (this關(guān)鍵字) 賦值 給某個類變量或者對象的成員變量, 那在第二次標記時它將被移出“即將回收”的集 合; 如果對象這時候還沒有逃 脫, 那基本上它就真的要被回收了。
六、再談引用
在JDK1.2以前,Java中引用的定義很傳統(tǒng): 如果引用類型的數(shù)據(jù)中存儲的數(shù)值代表的是另一塊內(nèi)存的起始地址,就 稱這塊內(nèi)存代表著一個引用。這種定義有些狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態(tài)。 我 們希望能描述這一類對象: 當內(nèi)存空間還足夠時,則能保存在內(nèi)存中;如果內(nèi)存空間在進行垃圾回收后還是非常緊 張,則可以拋棄這些對象。很多系統(tǒng)中的緩存對象都符合這樣的場景。 在JDK1.2之后,Java對引用的概念做了擴 充,將引用分為 強引用(Strong Reference) 、 軟引用(Soft Reference) 、 弱引用(Weak Reference) 和 虛引 用(Phantom Reference) 四種,這四種引用的強度依次遞減。
1、強引用
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內(nèi)存空間不足,Java虛擬 機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足的問 題。 ps:強引用其實也就是我們平時A a = new A()這個意思。
2、軟引用
如果一個對象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對 象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。 軟引用可以和一個引用隊列 (ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到 與之關(guān)聯(lián)的引用隊列中。
3、弱引用
用來描述那些非必須對象, 但是它的強度比軟引用更弱一些, 被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā) 生為止。 當垃圾收集器開始工作, 無論當前內(nèi)存是否足夠, 都會回收掉只 被弱引用關(guān)聯(lián)的對象。 在JDK 1.2版之 后提供了WeakReference類來實現(xiàn)弱引用。 弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用 所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中。
弱引用與軟引用的區(qū)別在于: ①更短暫的生命周期; ②一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。
4、虛引用
“虛引用”顧名思義,它是最弱的一種引用關(guān)系。如果一個對象僅持有虛引用,在任何時候都可能被垃圾回收器回 收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
虛引用與軟引用和弱引用的一個區(qū)別在于: ①虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用。 ②當垃圾回收器準備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前,把這個虛引用加入到 與之 關(guān)聯(lián)的引用隊列中。