Effective Java Item 6 note

Eliminate obsolete object references

Arwii
mycodingjourney
8 min readMay 24, 2018

--

這篇開始之前先來複習一下Java上面的變數,底下的內容都是參考深入淺出Java的第3,4,9章的整理

有兩種:

  • Primitive type

int test = 10;

分別的意思是

型別,名稱,派值給變數

此時的test就是10的位元組值。

若要比較兩個primitive的話==就可以了。

  • Reference

沒有物件變數這樣的東西存在。

只有reference到物件的變數。

物件參考變數保存的是存取物件的方法。

物件參考變數也不是物件的容器,而是類似指向物件的指標,或者可以說是位址。但在Java中我們不會也不需要也不該知道reference variable裡面實際裝的是什麼,只有JVM才會知道要怎麼去使用參考來取得該物件。(我們也不需要在乎參考變數佔用多少大小)

物件只會存在可以回收的heap上面。

若要比較兩個物件的話,可以用==,但如果要比較兩個物件的內容物的話,要用equals。(記得equals裡面也要實作你覺得怎樣叫做相等)

根據書上的說法,可以想像物件參考變數就是個遙控器。參考底下程式碼,可以分三步驟了解這個過程。

  1. Dog myDog 宣告一個reference variable,要求JVM分配空間給此參考變數,如果這個參考變數宣告在method裡面,空間就會在stack裡。如果這個參考變數宣告在class內(也就是instance variable),空間就會跟在該class物件產生出來的時候。(產生的大小就是全部在這個class內的instance variable)
  2. new Dog(); 要求JVM分配heap上的空間給此物件。
  3. = 連接object跟reference

Java是pass-by-value(pass-by-copy)

試考慮兩種情況

前面有提到了stack跟heap,

stack的記憶體位置存放的是method的呼叫跟local variable。而stack -> FILO

heap的記憶體位置存放的是new出來的物件。而gc就負責處理heap的空間。

那麽物件會在heap上面存活多久呢?

基本上只要有reference到該物件,就會存活著,也可以分兩種情形。

  1. reference跟物件是在method的scope中被產生跟連結,則離開該method,沒有reference指到物件了,此物件就等著被回收。
  2. 如果reference是屬於class的,那麼就要看該物件是什麼時候產生,以及還有多少reference指到它,來決定回收的時間

體會了這個第二點就可以開始討論這個item 6所要表達的東西

因為Java裡面有gc,感覺好像不用去管理記憶體相關的問題,但這個例子就是第一種Mem leak的例子,告訴我們在撰寫程式的時候還是要小心(程式碼附在github https://bit.ly/2J8VihU )。

但書上有提到,也不需要到對於每個reference沒用到的時候就=null清空,這樣會把程式碼弄亂,清空reference應該把它當成種例外,不是個規範,消除reference的最好方式是讓reference跟著scope去結束(see item 45)

當如果你的class是自己管理內存的,像是上面的例子一樣,自己要知道會不會有佔著不放的情況出現。

第二種Mem leak的例子是cache

建議可以用WeakHashMap來處理,應該可以看看google的cache library是怎麼做的 ( https://github.com/google/guava )

或者是也可以定期用background process去清理不需要的cache

我想android上面的各種imageloader的library的作法應該也是類似(https://github.com/nostra13/Android-Universal-Image-Loader)

另外值得一提的是WeakHashMap的使用情境跟相關的reference types(ref : https://blog.csdn.net/xlinsist/article/details/57089288)

Java中有4種reference

  • String reference:

一般的使用物件都是這種。

宣告方式:String test = “hello”;

  • Soft reference:

起因就是上面第一種類型的mem leak,把物件放在了class裡面,然後沒使用到的時候沒有手動清理,導致已經不用的物件一直佔著記憶體,導致頻繁的gc。那麼soft reference就是用在如果mem很充足的話就不會gc,而mem不夠的時候就會清理這類的mem。總之,在JVM丟出OOM之前一定會清理掉所有的soft references。不過這種方式就是把管理交給JVM。

宣告方式:SoftReference<byte[]> bufferRef;

要使用之前要從bufferRef.get(); 記得要判斷get出來的物件是不是null,如果是null就要新分配object了。

  • Weak reference:

起因是在疏忽下,導致object的生命週期比我們期望的還要長。可以參考文章內Socket的例子。當要用到的物件生命週期不是我們自己的application所能掌握的就有發生這種leak的可能性。那麼上面提到的WeakHashMap就可以來解決這種針對entry有不能掌握其生命週期的物件。

宣告方式:Map<Socket, User> connection = new WeakHashMap<Socket, User>();

  • Phantom reference:

如果你的class extends 了PhantomReference<Object> ,那麼在建構的時候需要傳入一個ReferenceQueue<Object>,就可以在對象object被回收的時候得知,然後你就可以做相對應的處理。

GC更詳盡的說明

https://hanxlinsist.github.io/Hotspot%E8%99%9A%E6%8B%9F%E6%9C%BA-%20%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E7%AE%97%E6%B3%95%E5%92%8C%E5%9E%83%E5%9C%BE%E6%94%B6%E9%9B%86%E5%99%A8/

簡單的心得是gc雖然有分不同廠商做出來的,但主要有三個工作:

  1. 分配memory
  2. 保證任何被引用的對象在mem中不會被gc掉
  3. 釋放不被引用的對象

然後會從GC roots(對象可以是1. Local variable and input para of the currently executing methods 2. Active threads 3. Static fields 4. JNI references) 做Marking and Sweeping.

裡面就有各種針對fragmentation的處理,對於效能的處理等等演算法。然後有針對年輕區域的minor gc跟整體包含老年區域的full gc。一個很重要的想法是,從這些想要優化演算法的觀點來看,就可以知道應該不會有一個是可以符合全部情況的gc,需要思考自己的應用程式跟硬體資源,去從time跟space找到一個tradeoff。那麼Oracle官方有建議一開始可以先讓JVM自己去調整GC,等到不合預期再去設定參數跟紀錄然後再反覆操作。

第三種Mem leak的例子是listener或者是callback,沒有被client反註冊,就有可能會造成leak,那麼就可以只留這些object的weak reference就好。

Android的framework也用到相當多weak reference,有興趣可以看看

Android上面有用到weak reference的例子

https://blog.csdn.net/u012951554/article/details/48055939

以上這些一步一步看下來對我來說才比較能完整的了解這個item的用意。

--

--

Arwii
mycodingjourney

Try not to become a man of success, but rather try to become a man of value — A. Einstein