誤區糾正:關於單例模式的內存分析


       小菜最近在讀《Java與模式》一書時,發現關於單例模式的章節中有這樣一段話:

       作者想表達的大意為:為了實現某個對象能夠持久在內存中,以供程序在整個運行周期都可以訪問,可以讓對象的某個成員變量持有一個指向自身的引用,來避免被回收。

       成員變量想要被清空,需要等待對象被釋放,而對象被釋放需要沒有引用指向它,此時成員變量恰恰指向了對象本身,這看起來很不錯,形成了一個循環。

       但實際上,這種說法是不准確的,容易讓讀者產生誤解。

       請看下邊這段代碼:

 1 package com.cnblogs.test;
 2 
 3 public class SingletonTest {
 4 
 5     public static void main(String[] args) {
 6         
 7         //調用測試方法
 8         test();
 9         //通知jvm回收無用資源
10         System.gc();
11         
12         System.out.println("main finalize ...");
13     }
14     
15     //測試方法
16     public static void test(){
17         //創建A類的對象
18         A a = new A();
19         //讓對象的成員變量指向其自身
20         a._a=a;
21     }
22     
23 }
24 
25 class A{
26     
27     //定義一個成員變量,用來保存對象本身
28     public A _a = null;
29     
30     //對象被銷毀時執行的方法
31     protected void finalize() throws Throwable {
32         System.out.println("A finalize ...");
33     }
34 }

 

 

       簡單說明一下:

       當在main方法中調用test方法時,test方法會創建一個A類的實例a,同時把實例a的堆區地址放在實例a的成員變量_a中,也就是在模擬成員變量持有指向自身對象的引用。

       當以上步驟執行完成后,test方法結束, 由於a是局部變量,保存在方法棧中,會被立即釋放,不再指向A類的實例,但是我們剛剛完成了“自引用”,根據上邊的理論,有引用指向A類的實例,實例便不會被釋放,因此上邊程序的輸出結果是“main 方法執行完畢...”。

       遺憾的是,結果並不是這樣,真實的輸出結果為:“main 方法執行完畢...A 被回收...”。

       為什么會這樣?聽聽小菜的解釋。

       單例模式創建的對象能夠一直存在於內存中不被釋放,並不只是由於持有一個自身的引用,本質是因為這個引用是靜態的!也就是說,如果成員變量是非靜態的,它持有一個自身的引用,那么這個對象還是會被回收。

     “系統內至少保持一個對對象的引用”,這個引用指的是從棧區或方法區中發出的引用,也就是安全的引用。

     類被實例化之后,是放在堆區中的,而我們是無法直接操作堆內存的!因此需要一個引用,指向堆區的某個區域,而這個引用,必須是從棧中(或方法區中)發出的,因為我們可以直接訪問棧內存,如果是從堆中發出的引用,是無意義的引用,我們根本訪問不到,因此會被回收。

     類的成員變量恰恰是放在堆內存中,因此由類的成員變量持有一個對象的引用,這個引用是不安全的(不安全≠無效)!!

     再舉個例子,如果棧區的a局部變量指向堆中的b對象,b對象的某個成員變量指向堆中的c對象。我們可以通過a訪問b,再由b訪問c。假如一旦a不再指向b,那么再也訪問不到b,c也不可能被訪問到,即便b此時仍然指向c,但都成了無法訪問的對象,b、c均會被jvm自動回收。

     單例模式中成員變量是靜態的,它並不保存在堆內存中,而是在方法區中,是一塊持久的內存空間,不會被自動回收,因此指向自身的引用是安全的,自身不會被回收。

     到此,如果我們把第一個例子中的成員變量改成靜態的,那么test方法執行結束后,A類的對象不會被回收,程序輸出結果為:“main 方法執行完畢...”。

     最后提一句,堆區是整個程序共享的,因此可能會出現線程安全問題。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM