Java與C++ 之間有一堵由內(nèi)存動態(tài)分配和垃圾收集技術(shù)所圍成的高墻,墻外的人想進去务漩,墻外的人想出來巡蘸。
垃圾收集大部分人都吧這項技術(shù)當做Java語言的伴生產(chǎn)物。
前面介紹了Java內(nèi)存運行時各區(qū)域的各個部分俭驮,其中程序計數(shù)器回溺、虛擬機棧、本地方法棧3各區(qū)域隨線程而生,隨線程而滅:棧中的棧幀隨著方法的進入和退出而有條不紊的執(zhí)行出棧和入棧操作馅而。每個棧幀中分配多少內(nèi)存基本上都是在類結(jié)構(gòu)確定下來時就已知的(盡管在運行時期會由JIT編譯器進行一些優(yōu)化,但在本章基于概念模型的談?wù)撝衅┦ィ篌w上可以認為是編譯器可知的)因此這幾個區(qū)域的內(nèi)存分配和回收都是具備確定性在這幾個區(qū)域內(nèi)就不需要過多久考慮回收的問題瓮恭。因為方法結(jié)束或者線程結(jié)束時,內(nèi)存就自然跟隨者回收了厘熟。而Java 方法區(qū)則不一樣屯蹦,一個接口中的多個實現(xiàn)類需要的內(nèi)存可能不一樣,一個方法中的多個分支需要的內(nèi)存也可能不一樣绳姨,我們只有在程序處于運行期間時才能知道會創(chuàng)建哪些對象登澜,這部分內(nèi)存的分配和回收都是動態(tài)的,垃圾回收機器所關(guān)注是這部分內(nèi)存飘庄。后續(xù)討論的也是這部分內(nèi)存
在堆里面存放著Java世界中幾乎所有的對象實例脑蠕,垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象之中哪些還“存活”著跪削,哪些已經(jīng)“死去”(即不可能再被任何途徑使用的對象)
引用計數(shù)法給對象中添加一個引用計數(shù)器谴仙,每當有一個地方引用它時,計數(shù)器就加1碾盐;當引用失效時晃跺,計數(shù)器就減一;任何時刻計數(shù)器為0的對象就是不可能被再使用的.主流的Java虛擬機里面沒有選用引用計數(shù)法來管理內(nèi)存毫玖,其中最主要的 原因就是很難解決對象之間相互循環(huán)引用的問題掀虎。
可達性分析算法在主流的商用程序語言的主流實現(xiàn)中,都是稱通過可達性分析來判斷對象是否存活的付枫。這個算法的基本思想就是通過一系列的稱為“GC roots”的對象作為起始點烹玉,從這些節(jié)點開始向下搜索,搜索走過的路徑稱為引用鏈励背,當一個對象到GC Roots沒有任何引用鏈相連春霍,則證明此對象不可用。
在Java語言中叶眉,可作為GC Roots的對象包括下面幾種:
1.虛擬機棧
2.方法區(qū)中類靜態(tài)屬性引用的對象址儒。
3.方法區(qū)中常量引用的對象
4.本地方法棧中JNI(Native方法)引用的對象。
無論是通過引用計數(shù)算法判斷對象的引用數(shù)量衅疙,還是通過可達性分析算法判斷對象的引用鏈是否可達莲趣,判定對象是否存活都與‘引用’有關(guān)。Java中的引用定義很傳統(tǒng):如果reference類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址饱溢,就稱這塊內(nèi)存代表著一個引用喧伞。JDK1.2之后,Java將引用分為強引用、軟引用潘鲫、弱引用翁逞、虛引用、溉仑。4種引用強度依次減弱挖函。
強引用:指程序代碼中普遍存在的,類型 Object obj = new Object(); 這類的引用浊竟。只要引用還存在怨喘,垃圾回收器永遠不會回收掉被引用的對象。
軟引用:是用來描述非必須對象的振定,對于軟引用關(guān)聯(lián)著的對象必怜,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收后频。如果這次回收還沒有足夠的內(nèi)存才會拋出內(nèi)存溢出異常梳庆。
弱引用是用來描述非必須對象的,但是他的強度比軟引用更弱一些卑惜,被弱引用關(guān)聯(lián)的對象只能夠生存到下一次垃圾回收之前靠益,當垃圾收集器工作時,無論當前內(nèi)存是否足夠残揉,都會回收掉只被弱引用關(guān)聯(lián)的對象胧后。
虛引用它是最弱的一種引用關(guān)系。一個對象是否有虛引用的存在抱环,完全不會對生存時間夠成影響壳快。,也無法通過虛引用取得一個對象實例镇草。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在對象被垃圾器回收時收到一個系統(tǒng)通知眶痰。
生存還是死亡:即時在可達性分析算法中不可達的對象,也并非是“非死不可”的梯啤,這時候他們暫時處于緩刑階段竖伯,要真正宣告一個對象死亡,至少要經(jīng)歷兩次標記過程:如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈因宇,那么它將會第一次標記且進行一次篩選七婴,篩選條件是次對象是否有必要執(zhí)行finalize()方法。當一個對象覆蓋finalize()方法察滑,或者finalize()方法已經(jīng)被虛擬機調(diào)用過打厘,虛擬機將這兩種情況視為“沒有必要執(zhí)行『爻剑”
如果這個對象被判定為有不要執(zhí)行finalize()方法户盯,那么這個對象將會放置在一個叫做F-Queue的隊列之中嵌施,并在稍后由虛擬機自動建立的,低優(yōu)先級的Finalizer線程去執(zhí)行它莽鸭,這些所謂的執(zhí)行是指虛擬機觸發(fā)這個方法吗伤,但不會承諾等待它運行結(jié)束,這樣做的原因是硫眨,如果一個對象在finalize()方法中執(zhí)行緩慢牲芋,或者發(fā)生了死循環(huán),將很有可能導(dǎo)致F-Queue隊列中其他對象用于處于等待捺球,甚至導(dǎo)致整個內(nèi)存回收系統(tǒng)奔潰。finalize()方法是對象逃脫死亡命運的最后一次機會夕冲,稍后GC將會對F-Queue中的對象進行第二次小規(guī)模標記氮兵,如果對象要在finalize()中成功拯救自己-只要重新與引用鏈上的任何一個對象建立關(guān)聯(lián)即可。譬如把自己賦值給某個類變量或者對象的成員變量歹鱼,那么在第二次標記時它將會被移除‘即將回收’的集合泣栈;如果對象這個時候還沒有逃脫,那基本上他就真的被回收了弥姻。
回收方法區(qū)Java虛擬機規(guī)范確實說過可以不要求虛擬機在方法區(qū)實現(xiàn)垃圾收集南片,而且在方法區(qū)中進行垃圾收集的‘性價比’一般較低;在堆中庭敦,尤其在新生代中疼进,常規(guī)應(yīng)用進行一次垃圾收集一般可以回收50%~95%的空間,而永久代的垃圾回收效率遠遠低于此秧廉。
永久代的垃圾回收集主要回收兩部分內(nèi)容=廢棄常量和無用的類伞广。回收廢棄常量與回收Java堆中的對象非常類似疼电。
以常量池中字面量的回收為例嚼锄,假如一個字符串“adc”已經(jīng)進常量池中,但是系統(tǒng)當前沒有任何一個String對象是叫做“abc”蔽豺,話句話說区丑,就是沒有任何String對象引用常量池中的“abc”常量,也沒有其他引用了這個字面量修陡,如果這是發(fā)生內(nèi)存回收沧侥,而且必要的話,“abc”常量會被系統(tǒng)清出常量池魄鸦。常量池中其他類(接口)正什、方法、字段的符號引用與此類似号杏。
判定一個類是否“無用的類”的條件則相對苛刻很多婴氮,同時滿足3個條件才能算是“無用的類”:
1.該類所有的實例都已經(jīng)被回收斯棒,也就是Java堆中不存在該類的任何實例。
2.加載該類的ClassLoader已經(jīng)回收主经。
3.該類對應(yīng)的java.lang.Class 對象沒有在任何地方被調(diào)用荣暮,無法在任何地方通過反射訪問該類的方法。
虛擬機可以對滿足上述3個條件的無用類進行回收罩驻,這里說的僅僅是“可以”穗酥,并不是和對象一樣,不使用必然回收惠遏。是否對類進行回收砾跃,HotSpot虛擬機提供了 -Xnocalssgc 參數(shù)進行控制。在大量使用反射节吮、動態(tài)代理抽高、CGLib等ByteCode框架、動態(tài)生成JSP以及OSGI這類頻繁自定義ClassLoader
的場景都需要虛擬機具備卸載的功能透绩,以保證永久代不會溢出翘骂。