Effective Java Item 6 note
Eliminate obsolete object references
這篇開始之前先來複習一下Java上面的變數,底下的內容都是參考深入淺出Java的第3,4,9章的整理
有兩種:
- Primitive type
int test = 10;
分別的意思是
型別,名稱,派值給變數
此時的test就是10的位元組值。
若要比較兩個primitive的話==就可以了。
- Reference
沒有物件變數這樣的東西存在。
只有reference到物件的變數。
物件參考變數保存的是存取物件的方法。
物件參考變數也不是物件的容器,而是類似指向物件的指標,或者可以說是位址。但在Java中我們不會也不需要也不該知道reference variable裡面實際裝的是什麼,只有JVM才會知道要怎麼去使用參考來取得該物件。(我們也不需要在乎參考變數佔用多少大小)
物件只會存在可以回收的heap上面。
若要比較兩個物件的話,可以用==,但如果要比較兩個物件的內容物的話,要用equals。(記得equals裡面也要實作你覺得怎樣叫做相等)
根據書上的說法,可以想像物件參考變數就是個遙控器。參考底下程式碼,可以分三步驟了解這個過程。
Dog myDog = new Dog();
- Dog myDog 宣告一個reference variable,要求JVM分配空間給此參考變數,如果這個參考變數宣告在method裡面,空間就會在stack裡。如果這個參考變數宣告在class內(也就是instance variable),空間就會跟在該class物件產生出來的時候。(產生的大小就是全部在這個class內的instance variable)
- new Dog(); 要求JVM分配heap上的空間給此物件。
- = 連接object跟reference
Java是pass-by-value(pass-by-copy)
試考慮兩種情況
int x = 1;
Dog myDog = new Dog();test1(x);
test2(myDog);void test1(int a) // a則是x的copy
void test2(Dog inputDog) // inputDog是myDog reference的copy。
前面有提到了stack跟heap,
stack的記憶體位置存放的是method的呼叫跟local variable。而stack -> FILO
heap的記憶體位置存放的是new出來的物件。而gc就負責處理heap的空間。
那麽物件會在heap上面存活多久呢?
基本上只要有reference到該物件,就會存活著,也可以分兩種情形。
- reference跟物件是在method的scope中被產生跟連結,則離開該method,沒有reference指到物件了,此物件就等著被回收。
- 如果reference是屬於class的,那麼就要看該物件是什麼時候產生,以及還有多少reference指到它,來決定回收的時間
體會了這個第二點就可以開始討論這個item 6所要表達的東西
因為Java裡面有gc,感覺好像不用去管理記憶體相關的問題,但這個例子就是第一種Mem leak的例子,告訴我們在撰寫程式的時候還是要小心(程式碼附在github https://bit.ly/2J8VihU )。
public Object popLeak() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
/*
問題點就在popLeak的return值,pop之後沒有去移除這個reference對於該物件的參考,
導致JVM一直以為該物件是有人reference的導致也不會去gc,以我自己現在的理解,
如果stack是個跟著application生命週期存在的物件,
那麼這些stack裡面的elements也都不會被gc。*/
}public Object popNotLeak() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
但書上有提到,也不需要到對於每個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更詳盡的說明
簡單的心得是gc雖然有分不同廠商做出來的,但主要有三個工作:
- 分配memory
- 保證任何被引用的對象在mem中不會被gc掉
- 釋放不被引用的對象
然後會從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的用意。