GC的基本原理

GC的基本原理

GC是垃圾收集的意思(GarbageCollection),內(nèi)存處理是編程人員容易出現(xiàn)問(wèn)題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動(dòng)監(jiān)測(cè)對(duì)象是否超過(guò)作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的,Java語(yǔ)言沒(méi)有提供釋放已分配內(nèi)存的顯示操作方法。

所以,Java的內(nèi)存管理實(shí)際上就是對(duì)象的管理,其中包括對(duì)象的分配和釋放。
對(duì)于程序員來(lái)說(shuō),分配對(duì)象使用new關(guān)鍵字;釋放對(duì)象時(shí),只要將對(duì)象所有引用賦值為null,讓程序不能夠再訪問(wèn)到這個(gè)對(duì)象,我們稱該對(duì)象為”不可達(dá)的”.GC將負(fù)責(zé)回收所有”不可達(dá)”對(duì)象的內(nèi)存空間。
對(duì)于GC來(lái)說(shuō),當(dāng)程序員創(chuàng)建對(duì)象時(shí),GC就開(kāi)始監(jiān)控這個(gè)對(duì)象的地址、大小以及使用情況。通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對(duì)象。通過(guò)這種方式確定哪些對(duì)象是”可達(dá)的”,哪些對(duì)象是”不可達(dá)的”.當(dāng)GC確定一些對(duì)象為”不可達(dá)”時(shí),GC就有責(zé)任回收這些內(nèi)存空間。但是,為了保證GC能夠在不同平臺(tái)實(shí)現(xiàn)的問(wèn)題,Java規(guī)范對(duì)GC的很多行為都沒(méi)有進(jìn)行嚴(yán)格的規(guī)定。例如,對(duì)于采用什么類(lèi)型的回收算法、什么時(shí)候進(jìn)行回收等重要問(wèn)題都沒(méi)有明確的規(guī)定。因此,不同的JVM的實(shí)現(xiàn)者往往有不同的實(shí)現(xiàn)算法。這也給Java程序員的開(kāi)發(fā)帶來(lái)行多不確定性。本文研究了幾個(gè)與GC工作相關(guān)的問(wèn)題,努力減少這種不確定性給Java程序帶來(lái)的負(fù)面影響。

增量式GC(IncrementalGC)

GC在JVM中通常是由一個(gè)或一組進(jìn)程來(lái)實(shí)現(xiàn)的,它本身也和用戶程序一樣占用heap空間,運(yùn)行時(shí)也占用CPU.當(dāng)GC進(jìn)程運(yùn)行時(shí),應(yīng)用程序停止運(yùn)行。因此,當(dāng)GC運(yùn)行時(shí)間較長(zhǎng)時(shí),用戶能夠感到Java程序的停頓,另外一方面,如果GC運(yùn)行時(shí)間太短,則可能對(duì)象回收率太低,這意味著還有很多應(yīng)該回收的對(duì)象沒(méi)有被回收,仍然占用大量?jī)?nèi)存。因此,在設(shè)計(jì)GC的時(shí)候,就必須在停頓時(shí)間和回收率之間進(jìn)行權(quán)衡。一個(gè)好的GC實(shí)現(xiàn)允許用戶定義自己所需要的設(shè)置,例如有些內(nèi)存有限有設(shè)備,對(duì)內(nèi)存的使用量非常敏感,希望GC能夠準(zhǔn)確的回收內(nèi)存,它并不在意程序速度的放慢。另外一些實(shí)時(shí)網(wǎng)絡(luò)游戲,就不能夠允許程序有長(zhǎng)時(shí)間的中斷。增量式GC就是通過(guò)一定的回收算法,把一個(gè)長(zhǎng)時(shí)間的中斷,劃分為很多個(gè)小的中斷,通過(guò)這種方式減少GC對(duì)用戶程序的影響。雖然,增量式GC在整體性能上可能不如普通GC的效率高,但是它能夠減少程序的最長(zhǎng)停頓時(shí)間。

SunJDK提供的HotSpotJVM就能支持增量式GC.HotSpotJVM缺省GC方式為不使用增量GC,為了啟動(dòng)增量GC,我們必須在運(yùn)行Java程序時(shí)增加-Xincgc的參數(shù)。HotSpotJVM增量式GC的實(shí)現(xiàn)是采用TrainGC算法。它的基本想法就是,將堆中的所有對(duì)象按照創(chuàng)建和使用情況進(jìn)行分組(分層),將使用頻繁高和具有相關(guān)性的對(duì)象放在一隊(duì)中,隨著程序的運(yùn)行,不斷對(duì)組進(jìn)行調(diào)整。當(dāng)GC運(yùn)行時(shí),它總是先回收最老的(最近很少訪問(wèn)的)的對(duì)象,如果整組都為可回收對(duì)象,GC將整組回收。這樣,每次GC運(yùn)行只回收一定比例的不可達(dá)對(duì)象,保證程序的順暢運(yùn)行。

為什么要分代

分代的垃圾回收策略,是基于這樣一個(gè)事實(shí):不同的對(duì)象的生命周期是不一樣的。因此,不同生命周期的對(duì)象可以采取不同的收集方式,以便提高回收效率。

在Java程序運(yùn)行的過(guò)程中,會(huì)產(chǎn)生大量的對(duì)象,其中有些對(duì)象是與業(yè)務(wù)信息相關(guān),比如Http請(qǐng)求中的Session對(duì)象、線程、Socket連接,這類(lèi)對(duì)象跟業(yè)務(wù)直接掛鉤,因此生命周期比較長(zhǎng)。但是還有一些對(duì)象,主要是程序運(yùn)行過(guò)程中生成的臨時(shí)變量,這些對(duì)象生命周期會(huì)比較短,比如:String對(duì)象,由于其不變類(lèi)的特性,系統(tǒng)會(huì)產(chǎn)生大量的這些對(duì)象,有些對(duì)象甚至只用一次即可回收。

試想,在不進(jìn)行對(duì)象存活時(shí)間區(qū)分的情況下,每次垃圾回收都是對(duì)整個(gè)堆空間進(jìn)行回收,花費(fèi)時(shí)間相對(duì)會(huì)長(zhǎng),同時(shí),因?yàn)槊看位厥斩夹枰闅v所有存活對(duì)象,但實(shí)際上,對(duì)于生命周期長(zhǎng)的對(duì)象而言,這種遍歷是沒(méi)有效果的,因?yàn)榭赡苓M(jìn)行了很多次遍歷,但是他們依舊存在。因此,分代垃圾回收采用分治的思想,進(jìn)行代的劃分,把不同生命周期的對(duì)象放在不同代上,不同代上采用最適合它的垃圾回收方式進(jìn)行回收。

虛擬機(jī)中的共劃分為三個(gè)代:年輕代(Young Generation)、年老點(diǎn)(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類(lèi)的類(lèi)信息,與垃圾收集要收集的Java對(duì)象關(guān)系不大。年輕代和年老代的劃分是對(duì)垃圾收集影響比較大的。

年輕代:

所有新生成的對(duì)象首先都是放在年輕代的。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對(duì)象。年輕代分三個(gè)區(qū)。一個(gè)Eden區(qū),兩個(gè)Survivor區(qū)(一般而言)。大部分對(duì)象在Eden區(qū)中生成。當(dāng)Eden區(qū)滿時(shí),還存活的對(duì)象將被復(fù)制到Survivor區(qū)(兩個(gè)中的一個(gè)),當(dāng)這個(gè)Survivor區(qū)滿時(shí),此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè)Survivor區(qū),當(dāng)這個(gè)Survivor去也滿了的時(shí)候,從第一個(gè)Survivor區(qū)復(fù)制過(guò)來(lái)的并且此時(shí)還存活的對(duì)象,將被復(fù)制“年老區(qū)(Tenured)”。需要注意,Survivor的兩個(gè)區(qū)是對(duì)稱的,沒(méi)先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí)存在從Eden復(fù)制過(guò)來(lái) 對(duì)象,和從前一個(gè)Survivor復(fù)制過(guò)來(lái)的對(duì)象,而復(fù)制到年老區(qū)的只有從第一個(gè)Survivor去過(guò)來(lái)的對(duì)象。而且,Survivor區(qū)總有一個(gè)是空的。同時(shí),根據(jù)程序需要,Survivor區(qū)是可以配置為多個(gè)的(多于兩個(gè)),這樣可以增加對(duì)象在年輕代中的存在時(shí)間,減少被放到年老代的可能。

年老代:

在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象,就會(huì)被放到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象。

持久代:(運(yùn)行時(shí)方法區(qū))

用于存放靜態(tài)文件,如今Java類(lèi)、方法等。持久代對(duì)垃圾回收沒(méi)有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來(lái)存放這些運(yùn)行過(guò)程中新增的類(lèi)。持久代大小通過(guò)-XX:MaxPermSize=進(jìn)行設(shè)置。

常量池指的是在編譯期被確定,并被保存在已編譯的.class文件中的一些數(shù)據(jù)。除了包含代碼中所定義的各種基本類(lèi)型(如:int、long等等)和對(duì)象型(如String及數(shù)組)的常量值(final)還包含一些以文本形式出現(xiàn)的符號(hào)引用,比如:
#類(lèi)和接口的全限定名
#字段的名稱的描述符
#方法和名稱的描述符
虛擬機(jī)必須為每個(gè)被裝載的類(lèi)型維護(hù)一個(gè)常量池。常量池就是該類(lèi)型所用到常量的一個(gè)有序集合,包括直接常量(string,integer和floating常量)和對(duì)其他類(lèi)型,字段和方法的符號(hào)引用。
對(duì)于String常量,它的值在常量池中的。而JVM中的常量池在內(nèi)存當(dāng)中是以表的形式存在的,對(duì)于String類(lèi)型,有一張固定長(zhǎng)度的CONSTANT_String_info表用來(lái)存儲(chǔ)文字字符串值,注意:該表只存儲(chǔ)文字字符串值,不存儲(chǔ)符號(hào)引用。
在程序執(zhí)行的時(shí)候,常量池會(huì)儲(chǔ)存在Method Area,而不是堆中

方法區(qū)和持久代的關(guān)系:
方法區(qū)物理上存在于堆里,而且是在堆的持久代里面;但在邏輯上,方法區(qū)和堆是獨(dú)立的。
一般說(shuō)堆的持久代就是說(shuō)方法區(qū),因?yàn)橐坏㎎VM把方法區(qū)(類(lèi)信息,常量池,靜態(tài)字段,方法)加載進(jìn)內(nèi)存以后,這些內(nèi)存一般是不會(huì)被回收的了。

用于存放靜態(tài)文件,如今Java類(lèi)、方法等。持久代對(duì)垃圾回收沒(méi)有顯著影響,但是有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些class,例如Hibernate等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來(lái)存放這些運(yùn)行過(guò)程中新增的類(lèi)。持久代大小通過(guò)-XX:MaxPermSize=進(jìn)行設(shè)置。

jdk8中的方法區(qū)

對(duì)于Java8, HotSpots取消了永久代,那么是不是也就沒(méi)有方法區(qū)了呢?當(dāng)然不是,方法區(qū)是一個(gè)規(guī)范,規(guī)范沒(méi)變,它就一直在。那么取代永久代的就是元空間。它可永久代有什么不同的?存儲(chǔ)位置不同,永久代物理是是堆的一部分,和新生代,老年代地址是連續(xù)的,而元空間屬于本地內(nèi)存;存儲(chǔ)內(nèi)容不同,元空間存儲(chǔ)類(lèi)的元信息,靜態(tài)變量和常量池等并入堆中。相當(dāng)于永久代的數(shù)據(jù)被分到了堆和元空間中。
JAVA8 JVM詳細(xì)說(shuō)明

什么情況下觸發(fā)垃圾回收

由于對(duì)象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時(shí)間也不一樣。GC有兩種類(lèi)型:Scavenge GC和Full GC

Scavenge GC
一般情況下,當(dāng)新對(duì)象生成,并且在Eden申請(qǐng)空間失敗時(shí),就會(huì)觸發(fā)Scavenge GC,對(duì)Eden區(qū)域進(jìn)行GC,清除非存活對(duì)象,并且把尚且存活的對(duì)象移動(dòng)到Survivor區(qū)。然后整理Survivor的兩個(gè)區(qū)。這種方式的GC是對(duì)年輕代的Eden區(qū)進(jìn)行,不會(huì)影響到年老代。因?yàn)榇蟛糠謱?duì)象都是從Eden區(qū)開(kāi)始的,同時(shí)Eden區(qū)不會(huì)分配的很大,所以Eden區(qū)的GC會(huì)頻繁進(jìn)行。因而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來(lái)。

Full GC
對(duì)整個(gè)堆進(jìn)行整理,包括Young、Tenured和Perm。Full GC因?yàn)樾枰獙?duì)整個(gè)對(duì)進(jìn)行回收,所以比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù)。在對(duì)JVM調(diào)優(yōu)的過(guò)程中,很大一部分工作就是對(duì)于FullGC的調(diào)節(jié)。有如下原因可能導(dǎo)致Full GC:

  • 年老代(Tenured)被寫(xiě)滿
  • 持久代(Perm)被寫(xiě)滿
  • System.gc()被顯示調(diào)用
  • 上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化

詳解finalize函數(shù)

finalize是位于Object類(lèi)的一個(gè)方法,該方法的訪問(wèn)修飾符為protected,由于所有類(lèi)為Object的子類(lèi),因此用戶類(lèi)很容易訪問(wèn)到這個(gè)方法。由于,finalize函數(shù)沒(méi)有自動(dòng)實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用,我們必須手動(dòng)的實(shí)現(xiàn),因此finalize函數(shù)的最后一個(gè)語(yǔ)句通常是super.finalize()。通過(guò)這種方式,我們可以實(shí)現(xiàn)從下到上實(shí)現(xiàn)finalize的調(diào)用,即先釋放自己的資源,然后再釋放父類(lèi)的資源。

根據(jù)Java語(yǔ)言規(guī)范,JVM保證調(diào)用finalize函數(shù)之前,這個(gè)對(duì)象是不可達(dá)的,但是JVM不保證這個(gè)函數(shù)一定會(huì)被調(diào)用。另外,規(guī)范還保證finalize函數(shù)最多運(yùn)行一次。
很多Java初學(xué)者會(huì)認(rèn)為這個(gè)方法類(lèi)似與C++中的析構(gòu)函數(shù),將很多對(duì)象、資源的釋放都放在這一函數(shù)里面。其實(shí),這不是一種很好的方式。原因有三,其一,GC為了能夠支持finalize函數(shù),要對(duì)覆蓋這個(gè)函數(shù)的對(duì)象作很多附加的工作。其二,在finalize運(yùn)行完成之后,該對(duì)象可能變成可達(dá)的,GC還要再檢查一次該對(duì)象是否是可達(dá)的。因此,使用finalize會(huì)降低GC的運(yùn)行性能。其三,由于GC調(diào)用finalize的時(shí)間是不確定的,因此通過(guò)這種方式釋放資源也是不確定的。
通常,finalize用于一些不容易控制、并且非常重要資源的釋放,例如一些I/O的操作,數(shù)據(jù)的連接。這些資源的釋放對(duì)整個(gè)應(yīng)用程序是非常關(guān)鍵的。在這種情況下,程序員應(yīng)該以通過(guò)程序本身管理(包括釋放)這些資源為主,以finalize函數(shù)釋放資源方式為輔,形成一種雙保險(xiǎn)的管理機(jī)制,而不應(yīng)該僅僅依靠finalize來(lái)釋放資源。

下面給出一個(gè)例子說(shuō)明,finalize函數(shù)被調(diào)用以后,仍然可能是可達(dá)的,同時(shí)也可說(shuō)明一個(gè)對(duì)象的finalize只可能運(yùn)行一次。


/**
* 
*/
class MyObject{   
  Test main;//記錄Test對(duì)象,在finalize中時(shí)用于恢復(fù)可達(dá)性
  public MyObject(Test t) {
    main = t;//保存Test對(duì)象
  }
  protected void finalize()
  {
    main.ref = this;//恢復(fù)本對(duì)象,讓本對(duì)象可達(dá)
    System.out.println("This is finalize");//用于測(cè)試finalize只運(yùn)行一次
  }
}
/**
* 
*/
class Test {
  MyObject ref;
  public static void main (String[]args){  
     Test test = new Test();
     test.ref = new MyObject(test);
     test.ref = null;//MyObject對(duì)象為不可達(dá)對(duì)象,finalize將被調(diào)用
     System.gc();
    if (test.ref != null) System.out.println("MyObject還活著");
}
}

//運(yùn)行結(jié)果:
//This is finalize

MyObject還活著:此例子中,需要注意的是雖然MyObject對(duì)象在finalize中變成可達(dá)對(duì)象,但是下次回收時(shí)候,finalize卻不再被調(diào)用,因?yàn)閒inalize函數(shù)最多只調(diào)用一次。

finalize的理解

Java有垃圾回收期負(fù)責(zé)回收無(wú)用對(duì)象占據(jù)的內(nèi)存資源。但也有特殊情況:假定你的對(duì)象(并非使用new)獲得了一塊“特殊”的內(nèi)存區(qū)域,由于垃圾回收器只知道釋放那些經(jīng)由new分配的內(nèi)存,所以它不知道該如何釋放該對(duì)象的這塊“特殊”內(nèi)存。gc只負(fù)責(zé)jvm內(nèi)部分配的資源,所以如果通過(guò)jni獲取了系統(tǒng)資源,肯定無(wú)法自動(dòng)回收., 為了應(yīng)對(duì)這種情況,java允許在類(lèi)中定義一個(gè)名為finalize()的方法。

它的工作原理“假定”是這樣的:

  • 一旦垃圾回收器準(zhǔn)備好釋放對(duì)象占用的存儲(chǔ)空間,將首先調(diào)用其finalize() 方法,并且在下一次垃圾回收動(dòng)作發(fā)生時(shí),才真正回收對(duì)象占用的內(nèi)存。

這里有一個(gè)編程陷阱:

  • 基于上述對(duì)finalize的理解,很容易把finalize和C++中的析構(gòu)函數(shù)對(duì)應(yīng)起來(lái)。其實(shí)他們兩個(gè)很不一樣,在C++中,對(duì)象一定會(huì)被銷(xiāo)毀;
  • 而java里的對(duì)象卻并非總是被垃圾回收器回收。也許你會(huì)發(fā)現(xiàn),只要程序沒(méi)有瀕臨存儲(chǔ)空間用完的那一刻,對(duì)象占用的空間就總也得不到釋放。
    (因?yàn)槔厥毡旧硪灿虚_(kāi)銷(xiāo),要是不使用它就不用支付這部分開(kāi)銷(xiāo)了)

finalize()的真正用途:

之所以要有finalize(),是由于在分配本地內(nèi)存時(shí)可能采用了類(lèi)似C語(yǔ)言中的做法,而非Java中的通常做法。這種情況主要發(fā)生在使用“本地方法”的情況下,本地方法是一種在Java中調(diào)用非Java代碼的方式。本地方法目前只支持C和C++,但它們可以調(diào)用其他語(yǔ)言寫(xiě)的代碼,所以實(shí)際上可以調(diào)用任何代碼。在非Java代碼中,也許會(huì)調(diào)用C的malloc()函數(shù)系列來(lái)分配存儲(chǔ)空間,而且除非調(diào)用了free()函數(shù),否則存儲(chǔ)空間將得不到釋放,從而造成內(nèi)存泄露。當(dāng)然,free()是C和C++中的函數(shù),所以要在finalize()中用本地方法調(diào)用它。

finalize()在什么時(shí)候被調(diào)用?

有三種情況
1.所有對(duì)象被Garbage Collection時(shí)自動(dòng)調(diào)用,比如運(yùn)行System.gc()的時(shí)候.
2.程序退出時(shí)為每個(gè)對(duì)象調(diào)用一次finalize方法。
3.顯式的調(diào)用finalize方法

何時(shí)及如何使用finalize

1 最重要的,盡量不要用finalize,太復(fù)雜了,還是讓系統(tǒng)照管比較好??梢远x其它的方法來(lái)釋放非內(nèi)存資源。
2 如果用,盡量簡(jiǎn)單。
3 如果用,避免對(duì)象再生,這個(gè)是自己給自己找麻煩。
4 可以用來(lái)保護(hù)非內(nèi)存資源被釋放。即使我們定義了其它的方法來(lái)釋放非內(nèi)存資源,但是其它人未必會(huì)調(diào)用該方法來(lái)釋放。在finalize里面可以檢查一下,如果沒(méi)有釋放就釋放好了,晚釋放總比不釋放好。
5 即使對(duì)象的finalize已經(jīng)運(yùn)行了,不能保證該對(duì)象被銷(xiāo)毀。要實(shí)現(xiàn)一些保證對(duì)象徹底被銷(xiāo)毀時(shí)的動(dòng)作,只能依賴于java.lang.ref里面的類(lèi)和GC交互了。

程序如何與GC進(jìn)行交互

Java2增強(qiáng)了內(nèi)存管理功能,增加了一個(gè)java.lang.ref包,其中定義了三種引用類(lèi)。這三種引用類(lèi)分別為SoftReference、WeakReference和PhantomReference.通過(guò)使用這些引用類(lèi),程序員可以在一定程度與GC進(jìn)行交互,以便改善GC的工作效率。這些引用類(lèi)的引用強(qiáng)度介于可達(dá)對(duì)象和不可達(dá)對(duì)象之間。

創(chuàng)建一個(gè)引用對(duì)象也非常容易,例如如果你需要?jiǎng)?chuàng)建一個(gè)SoftReference對(duì)象,那么首先創(chuàng)建一個(gè)對(duì)象,并采用普通引用方式(可達(dá)對(duì)象);然后再創(chuàng)建一個(gè)SoftReference引用該對(duì)象;最后將普通引用設(shè)置為null.通過(guò)這種方式,這個(gè)對(duì)象就只有一個(gè)SoftReference引用。同時(shí),我們稱這個(gè)對(duì)象為SoftReference對(duì)象。

SoftReference的主要特點(diǎn)是據(jù)有較強(qiáng)的引用功能。只有當(dāng)內(nèi)存不夠的時(shí)候,才進(jìn)行回收這類(lèi)內(nèi)存,因此在內(nèi)存足夠的時(shí)候,它們通常不被回收。另外,這些引用對(duì)象還能保證在Java拋出OutOfMemory異常之前,被設(shè)置為null.它可以用于實(shí)現(xiàn)一些常用圖片的緩存,實(shí)現(xiàn)Cache的功能,保證最大限度的使用內(nèi)存而不引起OutOfMemory.以下給出這種引用類(lèi)型的使用偽代碼;

//申請(qǐng)一個(gè)圖像對(duì)象
Image image = new Image();//創(chuàng)建Image對(duì)象
  …
//使用image
  …
//使用完了image,將它設(shè)置為soft引用類(lèi)型,并且釋放強(qiáng)引用;
SoftReference sr = new SoftReference(image);
image = null;
  …
//下次使用時(shí)
if (sr != null)image = sr.get();
else {
//由于GC由于低內(nèi)存,已釋放image,因此需要重新裝載;
image = new Image();
sr = new SoftReference(image);
}

Weak引用對(duì)象與Soft引用對(duì)象的最大不同就在于:GC在進(jìn)行回收時(shí),需要通過(guò)算法檢查是否回收Soft引用對(duì)象,而對(duì)于Weak引用對(duì)象,GC總是進(jìn)行回收。Weak引用對(duì)象更容易、更快被GC回收。雖然,GC在運(yùn)行時(shí)一定回收Weak對(duì)象,但是復(fù)雜關(guān)系的Weak對(duì)象群常常需要好幾次GC的運(yùn)行才能完成。Weak引用對(duì)象常常用于Map結(jié)構(gòu)中,引用數(shù)據(jù)量較大的對(duì)象,一旦該對(duì)象的強(qiáng)引用為null時(shí),GC能夠快速地回收該對(duì)象空間。

Phantom引用的用途較少,主要用于輔助finalize函數(shù)的使用。Phantom對(duì)象指一些對(duì)象,它們執(zhí)行完了finalize函數(shù),并為不可達(dá)對(duì)象,但是它們還沒(méi)有被GC回收。這種對(duì)象可以輔助finalize進(jìn)行一些后期的回收工作,我們通過(guò)覆蓋Reference的clear()方法,增強(qiáng)資源回收機(jī)制的靈活性。

一些Java編程的建議

根據(jù)GC的工作原理,我們可以通過(guò)一些技巧和方式,讓GC運(yùn)行更加有效率,更加符合應(yīng)用程序的要求。一些關(guān)于程序設(shè)計(jì)的幾點(diǎn)建議:

1.最基本的建議就是盡早釋放無(wú)用對(duì)象的引用。大多數(shù)程序員在使用臨時(shí)變量的時(shí)候,都是讓引用變量在退出活動(dòng)域(scope)后,自動(dòng)設(shè)置為null.我們?cè)谑褂眠@種方式時(shí)候,必須特別注意一些復(fù)雜的對(duì)象圖,例如數(shù)組,隊(duì)列,樹(shù),圖等,這些對(duì)象之間有相互引用關(guān)系較為復(fù)雜。對(duì)于這類(lèi)對(duì)象,GC回收它們一般效率較低。如果程序允許,盡早將不用的引用對(duì)象賦為null.這樣可以加速GC的工作。

2.盡量少用finalize函數(shù)。finalize函數(shù)是Java提供給程序員一個(gè)釋放對(duì)象或資源的機(jī)會(huì)。但是,它會(huì)加大GC的工作量,因此盡量少采用finalize方式回收資源。

3.如果需要使用經(jīng)常使用的圖片,可以使用soft應(yīng)用類(lèi)型。它可以盡可能將圖片保存在內(nèi)存中,供程序調(diào)用,而不引起OutOfMemory.

4.注意集合數(shù)據(jù)類(lèi)型,包括數(shù)組,樹(shù),圖,鏈表等數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)對(duì)GC來(lái)說(shuō),回收更為復(fù)雜。另外,注意一些全局的變量,以及一些靜態(tài)變量。這些變量往往容易引起懸掛對(duì)象(danglingreference),造成內(nèi)存浪費(fèi)。

5.當(dāng)程序有一定的等待時(shí)間,程序員可以手動(dòng)執(zhí)行System.gc(),通知GC運(yùn)行,但是Java語(yǔ)言規(guī)范并不保證GC一定會(huì)執(zhí)行。使用增量式GC可以縮短Java程序的暫停時(shí)間。

Java的垃圾回收機(jī)制

a、 停止—復(fù)制(stop-and-copy):先暫停程序的運(yùn)行,然后將所有存活的對(duì)象從當(dāng)前堆復(fù)制到另一個(gè)堆,沒(méi)有復(fù)制的全部都是垃圾。當(dāng)對(duì)象被復(fù)制到新堆時(shí),它們是一個(gè)挨著一個(gè)的,緊湊的。效率很低:首先,得有兩個(gè)堆空間占用率200%;其次,垃圾較少時(shí),復(fù)制大量的活著的對(duì)象,是很大的浪費(fèi)。

b、 標(biāo)記—清掃(mark-and-sweep):從對(duì)戰(zhàn)和靜態(tài)存儲(chǔ)區(qū)出發(fā),遍歷所有的引用,進(jìn)而找出所有存活的對(duì)象,如果活著,就標(biāo)記。只有全部標(biāo)記完畢的時(shí)候,清理動(dòng)作才開(kāi)始。在清理的時(shí)候,沒(méi)有標(biāo)記的對(duì)象將會(huì)被釋放,不會(huì)發(fā)生任何膚質(zhì)動(dòng)作。但是盛夏的對(duì)空間是不連續(xù)的,垃圾回收器要是希望得到連續(xù)空間的話,就得重新整理剩下的對(duì)象。

c、 注意:“停止—復(fù)制”的意思是這種垃圾回收動(dòng)作不是在后臺(tái)進(jìn)行的;相反,垃圾回收動(dòng)作發(fā)生的同時(shí),程序?qū)?huì)被暫停。有人將垃圾回收視為低優(yōu)先級(jí)的后臺(tái)進(jìn)程,而事實(shí)上并不是這樣,當(dāng)可用內(nèi)存數(shù)量比較低的時(shí)候,Sun版本的垃圾回收器就會(huì)暫停運(yùn)行程序。同樣,“標(biāo)記-清掃”工作也必須在程序暫停的情況下才能進(jìn)行。

d、 在java虛擬機(jī)中,內(nèi)存分配是以較大的塊為單位的。每個(gè)塊內(nèi)都用相應(yīng)的代數(shù)(generation count)來(lái)記錄它是否還存活。代數(shù)隨著引用的次數(shù)而增加。垃圾回收器將對(duì)上次回收動(dòng)作之后的新分配的塊進(jìn)行整理。這對(duì)處理大量短命的臨時(shí)對(duì)象很有幫助。垃圾回收器會(huì)定期進(jìn)行完整的清理動(dòng)作——大型對(duì)象仍然不會(huì)被復(fù)制(只是代數(shù)增加),內(nèi)涵小型對(duì)象的那些塊則被復(fù)制并整理。Java虛擬機(jī)會(huì)進(jìn)行監(jiān)視,如果所有對(duì)象都很穩(wěn)定,垃圾回收器的效率降低的話,就切換到“標(biāo)記—清掃”方式;同樣,java虛擬機(jī)會(huì)追蹤“標(biāo)記—清掃”的效果,要是堆空間出現(xiàn)很多碎片,就會(huì)切換到“停止—復(fù)制”方式。這就是“自適應(yīng)”技術(shù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.什么是垃圾回收? 垃圾回收(Garbage Collection)是Java虛擬機(jī)(JVM)垃圾回收器提供...
    簡(jiǎn)欲明心閱讀 90,359評(píng)論 17 311
  • 這篇文章是我之前翻閱了不少的書(shū)籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 16,051評(píng)論 3 83
  • 原文閱讀 前言 這段時(shí)間懈怠了,罪過(guò)! 最近看到有同事也開(kāi)始用上了微信公眾號(hào)寫(xiě)博客了,挺好的~給他們點(diǎn)贊,這博客我...
    碼農(nóng)戲碼閱讀 6,150評(píng)論 2 31
  • 原文鏈接:https://www.cnblogs.com/byue/p/5734779.html 一個(gè)優(yōu)秀Java...
    Easy的幸福閱讀 585評(píng)論 0 2
  • 一. 垃圾回收的意義 在C++中,對(duì)象所占的內(nèi)存在程序結(jié)束運(yùn)行之前一直被占用,在明確釋放之前不能分配給其它對(duì)...
    Stan_Z閱讀 2,053評(píng)論 0 25

友情鏈接更多精彩內(nèi)容