鍍金池/ 教程/ Java/ 單例模式討論篇:?jiǎn)卫J脚c垃圾回收
訪(fǎng)問(wèn)者模式
訪(fǎng)問(wèn)者模式討論篇:java的動(dòng)態(tài)綁定與雙分派
責任連模式
迭代器模式
策略模式
命令模式
單例模式
建造者模式
解釋器模式
工廠(chǎng)方法模式
備忘錄模式
原型模式
單例模式討論篇:?jiǎn)卫J脚c垃圾回收
觀(guān)察者模式
模版方法模式
創(chuàng )建類(lèi)模式總結篇
抽象工廠(chǎng)模式
中介者模式

單例模式討論篇:?jiǎn)卫J脚c垃圾回收

Jvm的垃圾回收機制到底會(huì )不會(huì )回收掉長(cháng)時(shí)間不用的單例模式對象,這的確是一個(gè)比較有爭議性的問(wèn)題。將這一部分內容單獨成篇的目的也是為了與廣大博友廣泛的討論一下這個(gè)問(wèn)題。為了能讓更多的人看到這篇文章,請各位博友看完文章之后,點(diǎn)一下"頂",讓本篇文章排名盡量的靠前。筆者在此謝過(guò)。

討論命題:當一個(gè)單例的對象長(cháng)久不用時(shí),會(huì )不會(huì )被jvm的垃圾收集機制回收。

首先說(shuō)一下為什么會(huì )產(chǎn)生這一疑問(wèn),筆者本人再此之前從來(lái)沒(méi)有考慮過(guò)垃圾回收對單例模式的影響,直到去年讀了一本書(shū),《設計模式之禪》秦小波著(zhù)。在書(shū)中提到在j2ee應用中,jvm垃圾回收機制會(huì )把長(cháng)久不用的單例類(lèi)對象當作垃圾,并在cpu空閑的時(shí)候對其進(jìn)行回收。之前讀過(guò)的幾本設計模式的書(shū),包括《java與模式》,書(shū)中都沒(méi)有提到j(luò )vm垃圾回收機制對單例的影響。并且在工作過(guò)程中,也沒(méi)有過(guò)單例對象被回收的經(jīng)歷,加上工作中很多前輩曾經(jīng)告誡過(guò)筆者:盡量不要聲明太多的靜態(tài)屬性,因為這些靜態(tài)屬性被加載后不會(huì )被釋放。因此對jvm垃圾收集會(huì )回收單例對象這一說(shuō)法持懷疑態(tài)度。漸漸地,發(fā)現在同事中和網(wǎng)上的技術(shù)人員中,對這一問(wèn)題也基本上是鮮明的對立兩派。那么到底jvm會(huì )不會(huì )回收長(cháng)久不用的單例對象呢。

對這一問(wèn)題,筆者本人的觀(guān)點(diǎn)是:不會(huì )回收。

下面給出本人的測試代碼

    class Singleton {
        private byte[] a = new byte[6*1024*1024];
        private static Singleton singleton = new Singleton();
        private Singleton(){}

        public static Singleton getInstance(){
            return singleton;
        }
    }

    class Obj {
        private byte[] a = new byte[3*1024*1024];
    }

    public class Client{
        public static void main(String[] args) throws Exception{
            Singleton.getInstance();
            while(true){
                new Obj();
            }
        }
    }

本段程序的目的是模擬j2ee容器,首先實(shí)例化單例類(lèi),這個(gè)單例類(lèi)占6M內存,然后程序進(jìn)入死循環(huán),不斷的創(chuàng )建對象,逼迫jvm進(jìn)行垃圾回收,然后觀(guān)察垃圾收集信息,如果進(jìn)行垃圾收集后,內存仍然大于6M,則說(shuō)明垃圾回收不會(huì )回收單例對象。

運行本程序使用的虛擬機是hotspot虛擬機,也就是我們使用的最多的java官方提供的虛擬機,俗稱(chēng)jdk,版本是jdk1.6.0_12

運行時(shí)vm arguments參數為:-verbose:gc -Xms20M -Xmx20M,意思是每次jvm進(jìn)行垃圾回收時(shí)顯示內存信息,jvm的內存設為固定20M。

運行結果:

……

[Full GC 18566K->6278K(20352K), 0.0101066 secs]

[GC 18567K->18566K(20352K), 0.0001978 secs]

[Full GC 18566K->6278K(20352K), 0.0088229 secs]

……

從運行結果中可以看到總有6M空間沒(méi)有被收集。因此,筆者認為,至少在hotspot虛擬機中,垃圾回收是不會(huì )回收單例對象的。

后來(lái)查閱了一些相關(guān)的資料,hotspot虛擬機的垃圾收集算法使用根搜索算法。這個(gè)算法的基本思路是:對任何"活"的對象,一定能最終追溯到其存活在堆?;蜢o態(tài)存儲區之中的引用。通過(guò)一系列名為根(GC Roots)的引用作為起點(diǎn),從這些根開(kāi)始搜索,經(jīng)過(guò)一系列的路徑,如果可以到達java堆中的對象,那么這個(gè)對象就是"活"的,是不可回收的??梢宰鳛楦膶ο笥校?/p>

  • 虛擬機棧(棧楨中的本地變量表)中的引用的對象。
  • 方法區中的類(lèi)靜態(tài)屬性引用的對象。
  • 方法區中的常量引用的對象。
  • 本地方法棧中JNI的引用的對象。

方法區是jvm的一塊內存區域,用來(lái)存放類(lèi)相關(guān)的信息。很明顯,java中單例模式創(chuàng )建的對象被自己類(lèi)中的靜態(tài)屬性所引用,符合第二條,因此,單例對象不會(huì )被jvm垃圾收集。

雖然jvm堆中的單例對象不會(huì )被垃圾收集,但是單例類(lèi)本身如果長(cháng)時(shí)間不用會(huì )不會(huì )被收集呢?因為jvm對方法區也是有垃圾收集機制的。如果單例類(lèi)被收集,那么堆中的對象就會(huì )失去到根的路徑,必然會(huì )被垃圾收集掉。對此,筆者查閱了hotspot虛擬機對方法區的垃圾收集方法,jvm卸載類(lèi)的判定條件如下:

  • 該類(lèi)所有的實(shí)例都已經(jīng)被回收,也就是java堆中不存在該類(lèi)的任何實(shí)例。
  • 加載該類(lèi)的ClassLoader已經(jīng)被回收。
  • 該類(lèi)對應的java.lang.Class對象沒(méi)有任何地方被引用,無(wú)法在任何地方通過(guò)反射訪(fǎng)問(wèn)該類(lèi)的方法。

只有三個(gè)條件都滿(mǎn)足,jvm才會(huì )在垃圾收集的時(shí)候卸載類(lèi)。顯然,單例的類(lèi)不滿(mǎn)足條件一,因此單例類(lèi)也不會(huì )被卸載。也就是說(shuō),只要單例類(lèi)中的靜態(tài)引用指向jvm堆中的單例對象,那么單例類(lèi)和單例對象都不會(huì )被垃圾收集,依據根搜索算法,對象是否會(huì )被垃圾收集與未被使用時(shí)間長(cháng)短無(wú)關(guān),僅僅在于這個(gè)對象是不是"活"的。假如一個(gè)對象長(cháng)久未使用而被回收,那么收集算法應該是最近最長(cháng)未使用算法,最近最長(cháng)未使用算法一般用在操作系統的內外存交換中,如果用在虛擬機垃圾回收中,豈不是太不安全了?以上是筆者的觀(guān)點(diǎn)。

因此筆者的觀(guān)點(diǎn)是:在hotspot虛擬機1.6版本中,除非人為地斷開(kāi)單例中靜態(tài)引用到單例對象的聯(lián)接,否則jvm垃圾收集器是不會(huì )回收單例對象的。


參考文獻

Java虛擬機規范

Java hotspot虛擬機內存管理

Java編程思想

Java與模式

設計模式

設計模式之禪

深入理解java虛擬機