最近研究Java Cache實現,發現使用到了軟引用(SoftReference),不太理解,查閱了JDK文檔、代碼以及幾篇文章。做個小結,如有錯誤,歡迎指正。
之所以想學習一下Java的幾種參考型別,原因有兩個:
- 理解Java Cache實現、學習Java引用與Java記憶體回收機制的關係
記憶體資源是有限的,需要合理的利用。Cache不是僅僅HashMap那麼簡單,Java引用與Java記憶體回收機制也有非常緊密的關係。
- 避免對Java引用的錯誤使用
某個同事把5000+交易資料放到一個HashMap裡面,用一個Spring Singleton Bean的全域屬性指向該HashMap。大量運用這種技術,很快就報out of memory。再大的記憶體也架不住對記憶體的錯誤使用。理解原理有助於我們盡量少犯或不犯低級錯誤。
Java引用與Java記憶體回收機制的關係
當Java虛擬機器(JVM)覺得記憶體不夠用的時候,會觸發記憶體回收操作(GC),清除無用的對象,釋放記憶體。可是如何判斷一個對象是否是垃圾呢?其中的一個方法是計算指向該對象的引用數量,如果引用數量為0,那麼該對象就為垃圾(Thread對象是例外),否則還有用處,不能被回收。但是如果把引用數為0的對象都回收了,還是不能滿足記憶體需求怎麼辦?Java把引用分為4種類型,記憶體回收行程會嘗試回收只有弱引用的對象。
按照一個對象的引用可達(Reachable)強度,由強到弱分為5類,如下:
- 強可達(Strong Reachable)
在一個線程內,無需引用直接可達,新建立的對象是強可達的。
- 軟可達(Soft Reachable)
不是強可達的,但是通過一個軟引用(SoftReference)可達。
- 弱可達(Soft Reachable)
既不是強可達也不是軟可達,但是通過一個弱引用(WeakReference)可達。
- 虛可達(Phantom Reachable)
既不是強可達,不是軟可達,也不是弱可達,但是通過一個虛引用(PhantomReference)可達。
- 不可達(Unreachable)
沒有任何引用指向對象。
比較好、容易理解的是Java記憶體回收行程會優先清理可達強度低的對象。另外有兩個重要的點:
- 強可達的一定不會被清理
- JVM保證拋出out of memory之前,清理所有的軟引用對象
4種Java引用
在實現一個緩衝系統的時候,如果全部使用強引用,那麼你需要自己去手動的把某些引用clear掉(引用置位null),否則遲早會拋出out of memory錯誤。緩衝系統引入弱引用或者軟引用的唯一原因是,把引用clear的事情交由Java記憶體回收行程來處理,cache程式自己置身事外。
幾種弱引用的使用方式非常相近。下面分別介紹4種參考型別。
強引用(StrongReference)
我們平時申明變數使用的就是強引用,普通系統99%以上都是強引用。比如,String s = "Hello World"
弱引用(WeakReference)
記憶體回收行程某個時刻決定回收軟可達的對象的時候,會清理軟引用,並可選的把引用存放到一個引用隊列(ReferenceQueue)。
軟引用(SoftReference)
類似弱引用,只不過Java虛擬機器會盡量讓軟引用的存活時間長一些,迫不得已才清理。
虛引用(PhantomReference)
僅用來處理資源的清理問題,比Object裡面的finalize機制更靈活。get方法返回的永遠是null,Java虛擬機器不負責清理虛引用,但是它會把虛引用放到引用隊列裡面。
兩個測試例子
使用HashMap,會報out of memory錯誤。
public static void main(String[] args) {Map<String, String> list = new HashMap<String, String>();long i = 1;while (i < 100000000L) {list.put(String.valueOf(i),"JDJJDJJJJJJJJJJ%%%%%%%%JJJJJJJJJJJJJJJKKKKKKKKKKKKKKKKKJJJJJJ"+ "JJJKKKKKHDDDJDJDJDJDJDJDJDJJDJDJDJDJDJDJJDJDJDJDJJDJDJJJJJJJJJ"+ "JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ"+ "JJJJJJJJJJJJJJJJJJJJJJJJJJJJ");// 測試第一個是否依然存活if (i % 100000 == 0) {System.out.println(list.get(String.valueOf(1)));}i++;}}
使用WeakHashMap,不會報out of memory錯誤。
public static void main(String[] args) {Map<String, String> list = new WeakHashMap<String, String>();long i = 1;while (i < 100000000L) {list.put(String.valueOf(i),"JDJJDJJJJJJJJJJ%%%%%%%%JJJJJJJJJJJJJJJKKKKKKKKKKKKKKKKKJJJJJJ"+ "JJJKKKKKHDDDJDJDJDJDJDJDJDJJDJDJDJDJDJDJJDJDJDJDJJDJDJJJJJJJJJ"+ "JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ"+ "JJJJJJJJJJJJJJJJJJJJJJJJJJJJ");// 測試第一個是否依然存活if (i % 100000 == 0) {System.out.println(list.get(String.valueOf(1)));}i++;}}
小結
Java語言裡面數組(Array)、列表(List)、Map等容器,對裡面的每一個對象都有一個引用,大資料的情況下要小心記憶體泄露。弱引用只適合cache等特殊情境,對於那些一定不能Java讓記憶體回收行程回收的對象,要使用強引用。
參考串連
- 理解弱引用
- JDK java.lang.ref包文檔,以及類說明