PhatomReference的作用
Java對象的狀態(tài)
- 可達(dá)狀態(tài):有一個以上的引用變量引用(強(qiáng)引用);
- 可恢復(fù)狀態(tài):不存在任何引用,且JVM在回收之前會調(diào)用finalize()清理資源后才真正回收,如果此期間使對象重獲引用變?yōu)榭蛇_(dá)狀態(tài);
- 不可達(dá)狀態(tài):不存在任何引用,且JVM執(zhí)行完finalize()后仍無引用,變?yōu)椴豢蛇_(dá)狀態(tài),JVM開始回收該對象。
對象的周期階段
當(dāng)新建一個對象時,會置位該對象的一個內(nèi)部標(biāo)識finalizable,當(dāng)某一點(diǎn)GC檢查到該對象不可達(dá)時,就把該對象放入finalize queue(F queue),GC會在對象銷毀前執(zhí)行finalize方法并且清空該對象的finalizable標(biāo)識。
簡而言之,一個簡單的對象生命周期為,Unfinalized Finalizable Finalized Reclaimed。
Object.finalize()
對象的finalize()被JVM調(diào)用后,才算對象被銷毀。
一般對象通過覆寫Object.finalize(),實(shí)現(xiàn)在對象回收之前,對GC無法回收的內(nèi)存進(jìn)行釋放。
-
Object.finalize()是JVM調(diào)用的,而JVM在回收時不保證一定會調(diào)用它,并且不能確定執(zhí)行的時機(jī); - JVM執(zhí)行
Object.finalize()過程中會導(dǎo)致嚴(yán)重的內(nèi)存消耗和性能損失; - 由于需要覆寫才能被執(zhí)行,很可能在實(shí)現(xiàn)時重獲自身引用...不安全且效率低。
public class A {
static A a;
public void finalize() {
a = this;
}
}
上述代碼如果JVM調(diào)用了finalize(),那么原本應(yīng)該被回收的對象重生,GC無法回收該對象。
所以如果想通過Object.finalize()來實(shí)現(xiàn)對象相關(guān)資源的釋放是不可靠的。一般使用PhatomReference來完成對象回收之前的資源釋放。
關(guān)于finalize()不安全,效率低可以參考Effective Java Item7:Avoid Finalizers,解釋為什么finalize是不安全的,不建議使用
PhatomReference回收過程
PhatomReference與SoftReference(WeakReference)最大區(qū)別在于JVM回收它們所引用的對象進(jìn)行的處理:
JVM會把SoftReference(WeakReference)中refernet字段設(shè)置為null,然后再將其加入ReferenceQueue中,此時所引用的對象處于可回收的狀態(tài);
而JVM不會在把PhatomReference加入隊(duì)列前做相同的處理,而是直接加入,此時所引用的對象還不可回收。
referent字段是Reference中的私有字段。參考java中虛引用PhantomReference與弱引用WeakReference(軟引用SoftReference)的差別
大致的流程:
- JVM的DC線程發(fā)現(xiàn)只要虛引用的對象時,直接將虛引用加入隊(duì)列,此時PhatomReference所引用的對象還存在PhatomReference對他的引用;
- 當(dāng)程序通過隊(duì)列的poll(),將該虛引用從隊(duì)列中刪除時,它所引用的對象(包括虛引用本身)不再要引用,此時DC可以進(jìn)行回收。
綜上所述,可以看到JVM的GC線程不能夠?qū)μ撘每刂频膶ο筮M(jìn)行自動回收,而需要程序手動回收。
回收對象前的處理
虛引用即無法通過get()獲取對象的強(qiáng)引用,也無法自動被回收,那么它存在的意義呢?它的意義就是跟蹤對象回收活動,在對象被回收之前做一些處理。例如資源釋放等等。
了解了PhatomReference回收的過程,可以通過隊(duì)列的判斷(poll方法)在對象被回收之前進(jìn)行處理。這樣的實(shí)現(xiàn)不僅安全,而且高效。
注意在PhatomReference被加入隊(duì)列之前,所引用的對象已經(jīng)處于Reclaimed(或者說是不可及狀態(tài)),下一步就是被JVM回收,而并不是JVM調(diào)用finalize()才開始回收。所以在隊(duì)列中發(fā)現(xiàn)PhatomReference保證了對象已經(jīng)Finalized(銷毀,不可能再重獲)并且一定能被回收。
相比使用SoftReference或WeakReference去清理對象占用的相關(guān)資源(通過finalize方法)更加高效,而且實(shí)現(xiàn)起來更安全(finalize方法不一定執(zhí)行等因素)。
weakReference或SoftReference被放入綁定的隊(duì)列是因?yàn)槠湟玫膶ο罂杉靶园l(fā)生改變,處于可恢復(fù)狀態(tài)(也就是對象周期階段的finalizable),下一步就是JVM調(diào)用finalize(),最后回收。由于可以在finalize()中重獲引用,所以加入隊(duì)列不能作為對象被回收的依據(jù)。
總結(jié)
PhatomReference所引用的對象只有處于Reclaimed時才會被加入隊(duì)列(已經(jīng)被Finalized處于不可及狀態(tài))。此時可以在對象被回收之前執(zhí)行清理工作,比finalize()更加靈活,高效,安全。從而更精細(xì)的控制對象的生命周期。
Java應(yīng)用程序
Java應(yīng)用是一個進(jìn)程,都持有自己的虛擬機(jī)。而在Java編程中,由于虛擬機(jī)屬于單進(jìn)程多線程,所以并發(fā)編程是關(guān)于線程的開發(fā)。
例如通過Java內(nèi)置工具 jps指令可以打印出當(dāng)前正在運(yùn)行Java程序進(jìn)程ID號。
由此關(guān)于Java中Piped I/O必須在同一個虛擬機(jī)中的兩個線程才能用于傳輸信息。
而在Android開發(fā)中,Dalvik虛擬機(jī)啟動時可以通過-XX:HeapGrowthLimit來給Dalvik的GrowthLimit設(shè)置大小,因?yàn)槊總€應(yīng)用程序是一個進(jìn)程,都持有自己的Dalvik。
Android虛擬機(jī)
Android虛擬機(jī)主要有兩個,ART和Dalvik。
ART在GC上做的比Dalvik好太多了,不光是GC的效率,減少Pause時間,而且還在內(nèi)存分配上對大內(nèi)存的有單獨(dú)的分配區(qū)域,同時還能有算法在后臺做內(nèi)存整理,減少內(nèi)存碎片。
對于開發(fā)者來說ART下基本可以避免很多類似GC導(dǎo)致的卡頓問題了。另外根據(jù)谷歌自己的數(shù)據(jù)來看,ART相對Dalvik內(nèi)存分配的效率提高了10倍,GC的效率提高了2-3倍。
以下參考自Android GC 那點(diǎn)事
Dalvik
Java堆
Dalvik運(yùn)行時數(shù)據(jù)區(qū)中堆得結(jié)構(gòu)主要由兩部分組成:Active堆和Zygote堆。
- Active堆,是Zygote進(jìn)程Fork第一個子進(jìn)程之前創(chuàng)建的。
- Zygote堆,用來管理Zygote進(jìn)程啟動過程中預(yù)加載和創(chuàng)建的對象。
對于Android操作系統(tǒng)來說,以后所有的應(yīng)用程序進(jìn)程都是被Zygote進(jìn)程Fork出來的,并且持有各自的Dalvik虛擬機(jī)。
Fork是類Unix系統(tǒng)創(chuàng)建新進(jìn)程的方法。如果需要創(chuàng)建新進(jìn)程,就通過Fork來創(chuàng)建一個自身的副本,該副本就是子進(jìn)程,而且會創(chuàng)建單獨(dú)的地址空間。這樣一來確保子進(jìn)程擁有父進(jìn)程所有內(nèi)存段的精確副本。
Cow策略
在創(chuàng)建應(yīng)用程序的過程中,Dalvik虛擬機(jī)采用Cow策略復(fù)制Zygote進(jìn)程的地址空間。
Cow策略:當(dāng)未復(fù)制Zygote進(jìn)程的地址空間時,Zygote和應(yīng)用進(jìn)程使用同一塊分配對象的堆。當(dāng)應(yīng)用進(jìn)程或Zygote進(jìn)程對該堆進(jìn)行寫操作時,內(nèi)核開始執(zhí)行真正的復(fù)制操作。使得Zygote和應(yīng)用進(jìn)程擁有各自的拷貝。
為什么是寫操作時開始復(fù)制
是因?yàn)樵谝粔K共享內(nèi)存上,有一方修改了共享變量的值,為了實(shí)現(xiàn)數(shù)據(jù)同步(屬于內(nèi)核操作),必須進(jìn)行復(fù)制,確保共享變量在每一方中副本相同。
也就是在創(chuàng)建應(yīng)用程序時,對堆內(nèi)存進(jìn)行寫操作。此時內(nèi)核就開始執(zhí)行復(fù)制操作,保證子進(jìn)程擁有父進(jìn)程精確副本。這個過程就是Zygote進(jìn)程Fork子進(jìn)程,采用Cow策略將Zygote進(jìn)程的內(nèi)容拷貝給子進(jìn)程。
減少或避免Copy
因?yàn)閺?fù)制操作十分耗時,所以應(yīng)該盡量去減少或避免它,所以Dalvik運(yùn)行時數(shù)據(jù)區(qū)中堆內(nèi)存劃分為Active和Zygote堆。
在Zygote進(jìn)程Fork第一個子進(jìn)程之前,把堆內(nèi)存劃分為兩塊:已使用部分和未使用部分。分別稱為Zygote堆和Active堆。
這樣做的目的是每當(dāng)創(chuàng)建一個子進(jìn)程時,只需要把Zygote堆中的內(nèi)容復(fù)制給應(yīng)用程序進(jìn)程。而后續(xù)Zygote進(jìn)程和應(yīng)用程序進(jìn)程都是在Active堆中分配對象。減少了Zygote堆的寫操作,從而減少了執(zhí)行寫時拷貝的操作(后續(xù)都是在Active中執(zhí)行寫,內(nèi)核拷貝的也是Active堆內(nèi)容)。
這樣的劃分還有一個好處,Zygote堆內(nèi)是Zygote進(jìn)程啟動過程中預(yù)加載的類,資源和對象。意味著它們可以在Zygote進(jìn)程和應(yīng)用程序進(jìn)程中做到長期共享,還能減少對內(nèi)存的需求。
GC指標(biāo)
- Starting Size,在啟動Dalvik時,系統(tǒng)分配一塊初始大小堆內(nèi)存給虛擬機(jī)使用??梢酝ㄟ^
-Xms設(shè)置; - GrowthLimit,是系統(tǒng)給每一個程序的最大堆上限,超過這個上限,程序就會OOM??梢酝ㄟ^
-XX:HeapGrowthLimit設(shè)置; - Maximum Size,不受控情況下的最大堆內(nèi)存大小,起始就是我們在用largeheap屬性的時候,可以從系統(tǒng)獲取的最大堆大小??梢酝ㄟ^
-Xms設(shè)置。
設(shè)置不同堆內(nèi)存大小是通過不同的指令,在虛擬機(jī)啟動時設(shè)置的。
最熟悉的就是在AndroidMainFest.xml文件中給Maximum Size賦值largeheap屬性獲取到系統(tǒng)分配給應(yīng)用最大堆內(nèi)存的大小。如果Android在分配對象內(nèi)存時,超過了Maximum,就會OOM。
還有一些指標(biāo):
- MinFree,堆最小利用率;
- MaxFree,堆最大利用率;
- TargetUtilization,目標(biāo)利用率。
假設(shè)當(dāng)前虛擬機(jī)GC后,堆中存活對象的占用內(nèi)存大小為LiveSize,那么該堆內(nèi)存的理想大小應(yīng)為(LiveSize / TargetUtilization)。但是理想大小不應(yīng)該小于(MinFree + LiveSize),不應(yīng)該大于(MaxFree + LiveSize)。每次虛擬機(jī)GC后都會盡量把堆利用率向目標(biāo)利用率靠攏。
當(dāng)程序去嘗試給大對象分配堆內(nèi)存,甚至去擴(kuò)大堆內(nèi)存大小。此時虛擬機(jī)GC活動,存活的對象變少了(例如局部變量由于方法的出棧被清除等,導(dǎo)致引用的對象被回收)。但是虛擬機(jī)的目標(biāo)利用率沒有改變,而GC就是為了使堆內(nèi)存利用率向目標(biāo)利用率靠攏,導(dǎo)致堆內(nèi)存擴(kuò)容失敗,甚至減小,從而導(dǎo)致頻繁的GC。
GC類型
- GC_FOR_MALLOC,在堆內(nèi)存上分配對象內(nèi)存不足時觸發(fā)GC;
- GC_CONCURRENT,當(dāng)應(yīng)用程序的堆內(nèi)存達(dá)到一定量,或者可以理解為快要滿的時候,系統(tǒng)會自動觸發(fā)GC操作來釋放內(nèi)存;
- GC_BEFORE_OOM,在準(zhǔn)備拋OOM異常之前進(jìn)行的最后努力而觸發(fā)的GC;
- GC_EXPLICT,程序調(diào)用
System.gc,VMRuntime.gc方法或者收到SIGUSR1信號時觸發(fā)的GC。
前三者都是在給對象分配堆內(nèi)存過程中觸發(fā)的。并發(fā)GC和非并發(fā)GC的區(qū)別主要在于前者在GC過程中,有條件地掛起和喚醒非GC線程,而后者在執(zhí)行GC的過程中,一直都是掛起非GC線程的。從而可以看出并發(fā)GC的程序又更好的響應(yīng)性。
對象的分配和GC觸發(fā)時機(jī)
- 調(diào)用
dvmHeapSourceAlloc方法分配指定大小的堆內(nèi)存,成功返回地址給調(diào)用者。這個方法不會改變堆當(dāng)前大小,屬于輕量級內(nèi)存分配操作。 - 上一步失敗,那么執(zhí)行GC。如果GC線程正在運(yùn)行時,調(diào)用
dvmWaitForConcurrentGCToComplete等待GC完成;否則調(diào)用gcForMalloc執(zhí)行GC,并且傳入false參數(shù)表示不回收軟引用。 - GC完畢后,再次執(zhí)行第一步嘗試輕量級內(nèi)存分配操作,成功就返回地址給調(diào)用者。
- 上一步失敗,調(diào)用
dvmHeapSourceAllocAndGrow方法進(jìn)行分配。該方法首先會考慮將堆內(nèi)存當(dāng)前大小調(diào)整到Dalvik啟動時指定的Java堆最大值,然后去分配內(nèi)存。如果成功,就返回地址比給調(diào)用者。 - 上一步失敗,調(diào)用
gcForMalloc,并且傳入true參數(shù),回收SoftReference引用的對象。 - GC完畢后,再次調(diào)用
dvmHeapSourceAllocAndGrow方法進(jìn)行內(nèi)存分配。無論成功與否,就到此為止。

Java中對象的回收前提是對象無法到達(dá)GC Root,而非強(qiáng)引用更進(jìn)一步的去控制對象的生命周期(即使可以到達(dá)GC Root,也有可能被回收)。
綜上所述,在對象的分配中會導(dǎo)致GC,第一次分配對象失敗系統(tǒng)會觸發(fā)GC但是不回收Soft的引用,如果再次分配還是失敗系統(tǒng)就會將Soft的內(nèi)存也給回收。前者觸發(fā)的GC是GC_FOR_MALLOC類型的GC,后者是GC_BEFORE_OOM類型的GC。而當(dāng)內(nèi)存分配成功后,系統(tǒng)會判斷當(dāng)前的內(nèi)存占用是否是達(dá)到了GC_CONCURRENT的閥值,如果達(dá)到了那么又會觸發(fā)GC_CONCURRENT。
總結(jié)
所以對于Dalvik虛擬機(jī)的手機(jī)來說,我們首先要盡量避免掉頻繁生成很多臨時小變量(比如說:getView, onDraw等函數(shù)中new對象),另一個又要盡量去避免產(chǎn)生很多長生命周期的大對象。
ART
Java堆
ART運(yùn)行時數(shù)據(jù)區(qū)中Java堆主要劃分為:Image Space,Zygote Space,Allocation Space和LargeObject Space。
- Image Space,用于存放一些預(yù)加載的類;
- Zygote Space,作用與Dalvik中的Zygote堆一樣;
- Allocation Space,作用與Dalvik中的Active堆一樣;
- LargeObject Space,一些離散地址的集合,用來分配一些大對象從而提高了GC的管理效率和整體性能。
LargeObject Space好處
小時候都玩過俄羅斯方塊,為了存放更多的方塊,需要合理安排好方塊放置的位置和方塊的方向。Java堆內(nèi)存分配也類似,合理的安排好對象的地址空間,可以提高堆內(nèi)存的使用率,從而減少GC。

Dalvik使用標(biāo)記與清理算法,容易產(chǎn)生碎片。這時Active堆沒有合理的位置和空間(當(dāng)內(nèi)存中有大量不連續(xù)的小內(nèi)存段,圖中紅色)存放接下來的較大內(nèi)存(就像俄羅斯方塊游戲,無法將新的方塊填入有空隙的行,只能新建一個行,這樣使用率下降,需要去不斷的GC,釋放新的空間),再分配一個較大的對象時,例如decode一個圖片,很容易導(dǎo)致GC(去清理這些碎片)。

由于ART有了LargeObject Space,大對象都分配到該區(qū)域,而Allocation Space分配較小的對象,從而使整個內(nèi)存使用率更高(就像俄羅斯方塊一樣,內(nèi)存形狀固定,在Allocation Space中合理安排較小的對象,可以提升整塊內(nèi)存的覆蓋率,也就是俄羅斯方塊中的規(guī)則每一行都填滿可以有更多的空間放更多的方塊)。
上述圖中波浪線表示程序代碼,藍(lán)色方塊表示較大對象,紅色表示小對象,綠色表示大對象。
GC類型
- kGcCauseForAlloc: 當(dāng)要分配內(nèi)存的時候發(fā)現(xiàn)內(nèi)存不夠的情況下引起的GC,這種情況下的GC會Stop World.
- kGcCauseBackground: 當(dāng)內(nèi)存達(dá)到一定的閥值的時候會去出發(fā)GC,這個時候是一個后臺GC,不會引起Stop World.
- kGcCauseExplicit,顯示調(diào)用的時候進(jìn)行的gc,如果ART打開了這個選項(xiàng)的情況下,在system.gc的時候會進(jìn)行GC.
- 其他更多。
stop world 是指在進(jìn)行垃圾回收的時候,需要將所有正在執(zhí)行的線程暫停(Stop World),保證GC線程的運(yùn)行。
對象的分配和GC觸發(fā)時機(jī)
這點(diǎn)和Dalvik虛擬機(jī)一樣,可以回憶一下Dalvik知識點(diǎn)。
