博主最近在整理Java集合框架時,在整理到WeakHashMap的時候,覺得有必要先闡述一下Java的引用類型,故此先整理的這篇文章,希望各位多提提意見。
閒話不多說,直接進入主題。Java中提供了4個級別的引用:強應用、軟引用、弱引用和虛引用。這四個引用定義在java.lang.ref的包下。
就是指在程序代碼中普遍存在的,類似Object obj = new Object()這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。
強引用具備一下三個個特點:
1. 強引用可以直接訪問目標對象;
2. 強引用鎖指向的對象在任何時候都不會被系統回收。JVM寧願拋出OOM異常也不回收強引用所指向的對象;
3. 強應用可能導致內存洩露;
整個FinalReference類的定義如下(有些API中並沒有加入FinalReference類的說明,只能看源碼了):
package java.lang.ref;
/* Final references, used to implement finalization */
class FinalReference extends Reference {
public FinalReference(T referent, ReferenceQueue q) {
super(referent, q);
}
}
從類定義中可以看出,只有一個構造函數,根據所給的對象的應用和應用隊列構造一個強引用。
是用來描述一些還有用但並非必須的對象。對於軟引用關聯著的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。
對於軟引用關聯著的對象,如果內存充足,則垃圾回收器不會回收該對象,如果內存不夠了,就會回收這些對象的內存。在 JDK 1.2 之後,提供了 SoftReference 類來實現軟引用。軟引用可用來實現內存敏感的高速緩存。軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
案例1:
package collections.ref;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public class SoftRefTest
{
private static ReferenceQueue softQueue = new ReferenceQueue<>();
public static class MyObject{
@Override
protected void finalize() throws Throwable
{
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString()
{
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable
{
Reference obj = null;
@Override
public void run()
{
try
{
obj = (Reference)softQueue.remove();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
if(obj != null)
{
System.out.println("Object for SoftReference is "+obj.get());
}
}
}
public static void main(String[] args)
{
MyObject object = new MyObject();
SoftReference softRef = new SoftReference<>(object,softQueue);
new Thread(new CheckRefQueue()).start();
object = null; //刪除強引用
System.gc();
System.out.println("After GC: Soft Get= "+softRef.get());
System.out.println("分配大塊內存");
byte[] b = new byte[5*1024*928];
System.out.println("After new byte[]:Soft Get= "+softRef.get());
System.gc();
}
}
運行參數1:
-Xmx5M
運行結果1:
After GC: Soft Get= I am MyObject
分配大塊內存
MyObject's finalize called
Object for SoftReference is null
After new byte[]:Soft Get= null
運行參數2:
-Xmx5M -XX:PrintGCDetails
運行結果2:
[GC [PSYoungGen: 680K->504K(2560K)] 680K->512K(6144K), 0.0040658 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 8K->482K(3584K)] 512K->482K(6144K) [PSPermGen: 2491K->2490K(21504K)], 0.0188479 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
After GC: Soft Get= I am MyObject
分配大塊內存
[GC [PSYoungGen: 123K->64K(2560K)] 605K->546K(7680K), 0.0004285 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [PSYoungGen: 64K->64K(2560K)] 546K->546K(7680K), 0.0003019 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 64K->0K(2560K)] [ParOldGen: 482K->482K(4608K)] 546K->482K(7168K) [PSPermGen: 2493K->2493K(21504K)], 0.0094748 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC [PSYoungGen: 0K->0K(2560K)] 482K->482K(7680K), 0.0003759 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 482K->472K(5120K)] 482K->472K(7680K) [PSPermGen: 2493K->2493K(21504K)], 0.0101017 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
MyObject's finalize called
Object for SoftReference is null
After new byte[]:Soft Get= null
[GC [PSYoungGen: 122K->32K(2560K)] 5235K->5144K(7680K), 0.0004806 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC [PSYoungGen: 32K->0K(2560K)] [ParOldGen: 5112K->5112K(5120K)] 5144K->5112K(7680K) [PSPermGen: 2493K->2493K(21504K)], 0.0136270 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2560K, used 20K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 1% used [0x00000000ffd00000,0x00000000ffd05250,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 5120K, used 5112K [0x00000000ff800000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 5120K, 99% used [0x00000000ff800000,0x00000000ffcfe188,0x00000000ffd00000)
PSPermGen total 21504K, used 2500K [0x00000000fa600000, 0x00000000fbb00000, 0x00000000ff800000)
object space 21504K, 11% used [0x00000000fa600000,0x00000000fa871190,0x00000000fbb00000)
加入 -XX:PrintGCDetails參數運行可以更形象的看到GC回收的細節。
這個案例1中,首先構造MyObject對象,並將其賦值給object變量,構成強引用。然後使用SoftReference構造這個MyObject對象的軟引用softRef,並注冊到softQueue引用隊列。當softRef被回收時,會被加入softQueue隊列。設置obj=null,刪除這個強引用,因此,系統內對MyObject對象的引用只剩下軟引用。此時,顯示調用GC,通過軟引用的get()方法,取得MyObject對象的引用,發現對象並未被回收,這說明GC在內存充足的情況下,不會回收軟引用對象。
接著,請求一塊大的堆空間5*1024*928,這個操作會使系統堆內存使用緊張,從而產生新一輪的GC。在這次GC後,softRef.get()不再返回MyObject對象,而是返回null,說明在系統內存緊張的情況下,軟引用被回收。軟引用被回收時,會被加入注冊的引用隊列。
如果將上面案例中的數組再改大點,比如5*1024*1024,就會拋出OOM異常:
After GC: Soft Get= I am MyObject
分配大塊內存
MyObject's finalize called
Object for SoftReference is null
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at collections.ref.SoftRefTest.main(SoftRefTest.java:58)
軟引用主要應用於內存敏感的高速緩存,在android系統中經常使用到。一般情況下,Android應用會用到大量的默認圖片,這些圖片很多地方會用到。如果每次都去讀取圖片,由於讀取文件需要硬件操作,速度較慢,會導致性能較低。所以我們考慮將圖片緩存起來,需要的時候直接從內存中讀取。但是,由於圖片占用內存空間比較大,緩存很多圖片需要很多的內存,就可能比較容易發生OutOfMemory異常。這時,我們可以考慮使用軟引用技術來避免這個問題發生。SoftReference可以解決oom的問題,每一個對象通過軟引用進行實例化,這個對象就以cache的形式保存起來,當再次調用這個對象時,那麼直接通過軟引用中的get()方法,就可以得到對象中中的資源數據,這樣就沒必要再次進行讀取了,直接從cache中就可以讀取得到,當內存將要發生OOM的時候,GC會迅速把所有的軟引用清除,防止oom發生。
案例2:
public class BitMapManager {
private Map> imageCache = new HashMap>();
//保存Bitmap的軟引用到HashMap
public void saveBitmapToCache(String path) {
// 強引用的Bitmap對象
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 軟引用的Bitmap對象
SoftReference softBitmap = new SoftReference(bitmap);
// 添加該對象到Map中使其緩存
imageCache.put(path, softBitmap);
// 使用完後手動將位圖對象置null
bitmap = null;
}
public Bitmap getBitmapByPath(String path) {
// 從緩存中取軟引用的Bitmap對象
SoftReference softBitmap = imageCache.get(path);
// 判斷是否存在軟引用
if (softBitmap == null) {
return null;
}
// 取出Bitmap對象,如果由於內存不足Bitmap被回收,將取得空
Bitmap bitmap = softBitmap.get();
return bitmap;
}
}
用來描述非必須的對象,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發送之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。一旦一個弱引用對象被垃圾回收器回收,便會加入到一個注冊引用隊列中。
我們略微修改一下案例1的代碼,如下:
package collections.ref;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class WeakRefTest
{
private static ReferenceQueue weakQueue = new ReferenceQueue<>();
public static class MyObject{
@Override
protected void finalize() throws Throwable
{
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString()
{
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable
{
Reference obj = null;
@Override
public void run()
{
try
{
obj = (Reference)weakQueue.remove();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
if(obj != null)
{
System.out.println("刪除的弱引用為:"+obj+" but獲取弱引用的對象obj.get()="+obj.get());
}
}
}
public static void main(String[] args)
{
MyObject object = new MyObject();
Reference weakRef = new WeakReference<>(object,weakQueue);
System.out.println("創建的弱引用為:"+weakRef);
new Thread(new CheckRefQueue()).start();
object = null;
System.out.println("Before GC: Weak Get= "+weakRef.get());
System.gc();
System.out.println("After GC: Weak Get= "+weakRef.get());
}
}
不加參數運行結果:
創建的弱引用為:[email protected]
Before GC: Weak Get= I am MyObject
After GC: Weak Get= null
MyObject's finalize called
刪除的弱引用為:[email protected] but獲取弱引用的對象obj.get()=null
可以看到,在GC之前,弱引用對象並未被垃圾回收器發現,因此通過 weakRef.get()可以獲取對應的對象引用。但是只要進行垃圾回收,弱引用一旦被發現,便會立即被回收,並加入注冊引用隊列中。此時再試圖通過weakRef.get()獲取對象的引用就會失敗。
弱引用的相關實際案例可以參考WeakHashMap,博主會在近期整理出相關文檔。等不及的小伙伴可以自行度娘之。
軟引用、弱引用都非常適合來保存那些可有可無的緩存數據。如果這麼做,當系統內存不足時,這些緩存數據會被回收,不會導致內存溢出。而當內存資源充足時,這些緩存數據又可以存在相當長的時間,從而起來加速系統的作用。
虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個持有虛引用的對象,和沒有引用幾乎是一樣的,隨時都有可能被垃圾回收器回收。當試圖通過虛引用的get()方法取得強引用時,總是會失敗。並且,虛引用必須和引用隊列一起使用,它的作用在於跟蹤垃圾回收過程。
虛引用中get方法的實現如下:
public T get() {
return null;
}
可以看到永遠返回null.
我們再來修改一下案例1的代碼:
package collections.ref;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.TimeUnit;
public class PhantomRefTest
{
private static ReferenceQueue phanQueue = new ReferenceQueue<>();
public static class MyObject{
@Override
protected void finalize() throws Throwable
{
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString()
{
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable
{
Reference obj = null;
@Override
public void run()
{
try
{
obj = (Reference)phanQueue.remove();
System.out.println("刪除的虛引用為:"+obj+" but獲取虛引用的對象obj.get()="+obj.get());
System.exit(0);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException
{
MyObject object = new MyObject();
Reference phanRef = new PhantomReference<>(object,phanQueue);
System.out.println("創建的虛引用為:"+phanRef);
new Thread(new CheckRefQueue()).start();
object = null;
TimeUnit.SECONDS.sleep(1);
int i =1;
while(true)
{
System.out.println("第"+i+++"次gc");
System.gc();
TimeUnit.SECONDS.sleep(1);
}
}
}
運行結果:
創建的虛引用為:[email protected]
第1次gc
MyObject's finalize called
第2次gc
刪除的虛引用為:[email protected] but獲取虛引用的對象obj.get()=null
可以看到,再經過一次GC之後,系統找到了垃圾對象,並調用finalize()方法回收內存,但沒有立即加入回收隊列。第二次GC時,該對象真正被GC清楚,此時,加入虛引用隊列。
虛引用的最大作用在於跟蹤對象回收,清理被銷毀對象的相關資源。
通常當對象不被使用時,重載該對象的類的finalize方法可以回收對象的資源。但是如果使用不慎,會使得對象復活,譬如這麼編寫finalize方法:
public class Test{
public static Test obj;
@Override protected void finalize() throws Throwable{
super.finalize();
obj = this;
}
}
對上面這個類Test中obj = new Test();然後obj=null;之後調用System.gc()企圖銷毀對象,但是很抱歉,不管你調用多少次System.gc()都沒有什麼用,除非你在下面的代碼中再就obj=null;這樣才能回收對象,這是因為JVM對某一個對象至多只執行一次被重寫的finalize方法。
上面的小片段說明重寫finalize的方法並不是很靠譜,可以使用虛引用來清理對象所占用的資源。
如下代碼所示:
public class PhantomRefTest2
{
private static ReferenceQueue phanQueue = new ReferenceQueue<>();
private static Map,String> map = new HashMap<>();
public static class MyObject{
@Override
protected void finalize() throws Throwable
{
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString()
{
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable
{
Reference obj = null;
@Override
public void run()
{
try
{
obj = (Reference)phanQueue.remove();
Object value = map.get(obj);
System.out.println("clean resource:"+value);
map.remove(obj);
System.out.println("刪除的虛引用為:"+obj+" but獲取虛引用的對象obj.get()="+obj.get());
System.exit(0);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException
{
MyObject object = new MyObject();
Reference phanRef = new PhantomReference<>(object,phanQueue);
System.out.println("創建的虛引用為:"+phanRef);
new Thread(new CheckRefQueue()).start();
map.put(phanRef, "Some Resources");
object = null;
TimeUnit.SECONDS.sleep(1);
int i =1;
while(true)
{
System.out.println("第"+i+++"次gc");
System.gc();
TimeUnit.SECONDS.sleep(1);
}
}
}
運行結果:
創建的虛引用為:[email protected]
第1次gc
MyObject's finalize called
第2次gc
clean resource:Some Resources
刪除的虛引用為:[email protected] but獲取虛引用的對象obj.get()=null