1. 程式人生 > >java的迴圈引用

java的迴圈引用

在C++中使用過智慧指標的同學們應該都清楚智慧指標對C++中記憶體管理帶來的極大便利,但是也會引入一些頭疼的問題,比如智慧指標帶來的迴圈引用的問題,這個問題在之前的專案中一直沒有很好的解決。
最近參與到android的專案開發,對java的記憶體的管理有了一個初步的瞭解,很容易想到了迴圈引用的問題。比如下面這個例子:

 public void buidDog()
        {
           Dog newDog = new Dog();
          Tail newTail = new Tail();
          newDog.tail = newTail;
         newTail.dog = newDog;
        }
    在這裡,newTail中拿著對newDog的引用,newDog中拿著對newTail的引用。如果newDog要被回收,前提是newTail被先回收,這樣才能釋放對newDog的引用。但是反回過來,newTail要被回收的前提是newDog要被先回收。當buildDog函式退出後,看起來垃圾回收管理似乎就始終無法回收這兩個實際已經不再需要的物件。
     垃圾回收機制究竟能否解決迴圈引用這一困境,帶著這個疑問找了一些資料,找到了一個比較滿意的解釋。在《Java Platform Performance: Strategies and Tactics》這本書的附錄A中有一處說明,這本書出自sun公司java團隊員工,應該算比較權威的。其中有這樣一段(http://java.sun.com/docs/books/performance/1st_edition/html/JPAppGC.fm.html#997428):

“It’s important to note that not just any strong reference will hold an object in memory. These must be references that chain from a garbage collection root. GC roots are a special class of variable that includes
Temporary variables on the stack (of any thread)
Static variables (from any class)
Special references from JNI native code”。
這段話可以簡單的理解就是強引用並不能保證物件不被回收。垃圾回收機制除了檢查物件是否被引用外,還要看物件是否被至少一個GC roots物件直接或者間接引用。GC roots物件包括以下一些類容:
1 每個執行緒當前的函式呼叫棧,從棧頂到棧底的每個函式裡的區域性變數。
2 靜態的變數
3 被jni中引用到的變數。
所以,上面例子中兩個迴圈引用的物件,雖然都存在一個強引用,但是不被任何GC root物件直接或者間接引用到,垃圾回收機制能夠發現這個問題。
另外,為了驗證這一點,特意翻看了一下android原始碼中GC管理這一塊的程式碼。在MarkSweep.c這檔案中,有一個void dvmHeapMarkRootSet()函式,這個函式對於GC root物件,有一些詳細的說明,有興趣的可以細看一下。
所以,java對於迴圈引用有一套自己的解決方案。但是話又說回來,一般實際編碼中出現的迴圈引用不會是上面那個例子那樣明顯,一般都是多個物件複雜的引用導致的迴圈,這個時候,如果一個物件的生命週期很長,就會導致多個物件都釋放不了,所以還是要特別留意物件之間的引用關係。