在Java中提供了4個級别的引用:強引用,軟引用,弱引用,虛引用。在這4個引用級别中,隻有強引用FinalReference類是包内可見,其他3中引用類型均為 public
,可以在應用程式中直接使用。
強引用
Java中的引用,有點像C++的指針,通過引用,可以對堆中的對象進行操作。
在我們的代碼生涯中,大部分使用的都是強引用,所謂強引入,都是形如
Object o = new Object()
的操作。
強引用具備一下特點:
- 強引用可以直接通路目标對象
- 強引用所指向的對象在任何時候不會被系統回收,JVM甯願抛出OOM異常,也不回收強引用所指向的對象
- 強引用可能導緻記憶體洩漏
是以當我們在使用強引用建立對象時,如果下面不使用這個對象了,一定要顯式地使用
o = null
操作來輔助垃圾回收器進行gc操作。一旦我們沒有進行置
null
操作,就會造成記憶體洩漏,再使用MAT等工具進行複雜操作,浪費了大量的時間。
在jdk代碼中也有許多顯式置
null
的操作。對于
ArrayList
,相信了解過java的都知道,
ArrayList
底層使用的數組實作的,在我們進行
clear
操作時,就會對數組進行置null操作。
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
其實如果我們的對象
Object o = new Object()
是在方法内建立的,那麼局部變量
o
将被配置設定在棧上,而對象
Object
執行個體被配置設定在堆上,局部變量
o
指向
Object
執行個體所在的對空間,通過
o
可以操作該執行個體,那麼
o
就是
Object
的引用。這時候顯式置
null
的作用不大,隻要在我們的方法退出,即該棧桢從Java虛拟機棧彈出時,
o
指向
Object
的引用就斷開了,此時
Object
在堆上配置設定的記憶體在GC時就能被回收。
軟引用
軟引用是除強引用外,最強的引用類型。可以通過
java.lang.ref.SoftReference
使用軟引用,一個持有軟引用的對象,不會被JVM很快回收,JVM會根據目前堆的使用情況來判斷何時回收,當堆使用率臨近門檻值時,才會去回收軟引用對象。隻要有足夠的記憶體,軟引用便可能在記憶體中存活相當長一段時間。是以,軟引用可以用于實作對記憶體敏感的Cache。
在java doc中,軟引用是這樣描述的
虛拟機在抛出 OutOfMemoryError 之前會保證所有的軟引用對象已被清除。此外,沒有任何限制保證軟引用将在某個特定的時間點被清除,或者确定一組不同的軟引用對象被清除的順序。不過,虛拟機的具體實作會傾向于不清除最近建立或最近使用過的軟引用。
舉個例子
/**
*
* @author xiaosuda
* @date 2018/10/23
*/
public class ReferenceTest {
public static void main(String[] args) {
//強引用
MyObject object = new MyObject();
//建立引用隊列
ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
//建立軟引用
SoftReference<MyObject> softRef = new SoftReference<>(object, queue);
//檢查引用隊列,監控對象回收情況
new Thread(new CheckRefQueue(queue)).start();
//删除強引用
object = null;
//手動GC
System.gc();
System.out.println("After GC:Soft Get= " + softRef.get());
System.out.println("配置設定大記憶體:" + Runtime.getRuntime().maxMemory());
try {
//配置設定大内容
byte[] maxObject = new byte[(int) Runtime.getRuntime().maxMemory()];
} catch (Throwable e) {
System.out.println(e.getMessage());
}
System.out.println("After new byte[]:Soft Get= " + softRef.get());
}
}
class CheckRefQueue implements Runnable {
ReferenceQueue queue;
public CheckRefQueue(ReferenceQueue queue) {
this.queue = queue;
}
@Override
public void run() {
Reference myObj = null;
try {
myObj = queue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (myObj != null) {
System.out.println("Object for SoftReference is " + myObj.get());
}
}
}
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";
}
}
JVM參數為:-Xmx5m -XX:+PrintGCDetails -Xmn2m
執行結果如下:
[GC (System.gc()) [PSYoungGen: 891K->496K(1536K)] 891K->520K(5632K), 0.0015474 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 496K->0K(1536K)] [ParOldGen: 24K->425K(4096K)] 520K->425K(5632K), [Metaspace: 2687K->2687K(1056768K)], 0.0050401 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
After GC:Soft Get= I'am MyObject
配置設定大記憶體:5767168
[GC (Allocation Failure) [PSYoungGen: 20K->64K(1536K)] 445K->489K(5632K), 0.0002611 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 64K->96K(1536K)] 489K->521K(5632K), 0.0002294 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 96K->0K(1536K)] [ParOldGen: 425K->414K(4096K)] 521K->414K(5632K), [Metaspace: 2688K->2688K(1056768K)], 0.0037219 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 414K->414K(5632K), 0.0007248 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 414K->402K(4096K)] 414K->402K(5632K), [Metaspace: 2688K->2688K(1056768K)], 0.0047190 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
異常:Java heap space
MyObject's finalize called
Object for SoftReference is null
After new byte[]:Soft Get= null
Heap
PSYoungGen total 1536K, used 81K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 1024K, 7% used [0x00000007bfe00000,0x00000007bfe14760,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
to space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
ParOldGen total 4096K, used 402K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
object space 4096K, 9% used [0x00000007bfa00000,0x00000007bfa649d8,0x00000007bfe00000)
Metaspace used 2694K, capacity 4490K, committed 4864K, reserved 1056768K
class space used 290K, capacity 386K, committed 512K, reserved 1048576K
Process finished with exit code 0
在這個例子中,首先構造對象,并将其指派給
MyObject
變量,構成強引用。然後使用弱引用構造這個
obj
對象的軟引用,并注冊導
MyObject
隊列裡面。當
softQueue
被回收時,會被
softRef
隊列,設定obj=null,删除這個強引用。是以,系統内對
softQueue
對象的引用隻剩下軟引用。此時顯示調用
MyObject
,通過軟引用的get方法,取得
GC
對象執行個體的強引用。法相對象未被回收。說明在
myObject
GC
充足情況下不會回收軟引用對象。
接着申請一塊堆大小的的堆空間,并
異常,從執行結果發現,在這次
catch
後,
GC
不再傳回
softRef.get()
對象,而是傳回
MyObject
null
。說明,在系統記憶體緊張的情況下,軟引用被回收并且加入注冊的引用隊列
軟引用在我們的日常開發中使用的場景很多,比如商城中商品的資訊。某個商品可能會被多人通路,此時我們可以把該商品的資訊使用軟引用儲存。當系統記憶體足夠時,可以實作高速查找,當系統記憶體不足又會被回收,避免
的風險。
OOM
TIPS: 盡管軟引用會在OOM之前被清理,但是,這并不表示full gc 會清理軟引用對象。在經過full gc後我們的軟引用對象都放入了old區,由于full gc的存在,程式大多數強框下并不會OOM。由于軟引用對象占據了老年代的空間,full gc将執行的更為頻繁。是以還是建議使用弱引用
當然了,上面的例子是
OOM
之前回收軟引用。怎麼才能
full gc
就回收軟引用對象呢?
-XX:SoftRefLRUPolicyMSPerMB // FullGC 保留的 SoftReference 數量,參數值越大,GC 後保留的軟引用對象就越多。
當我們設定這個參數值為0時,
full gc
就會回收我們的軟引用對象了
修改main方法内容
public static void main(String[] args) {
//強引用
MyObject object = new MyObject();
//建立引用隊列
ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
//建立軟引用
SoftReference<MyObject> softRef = new SoftReference<>(object, queue);
//檢查引用隊列,監控對象回收情況
new Thread(new CheckRefQueue(queue)).start();
//删除強引用
object = null;
//手動GC
System.gc();
System.out.println("After new byte[]:Soft Get= " + softRef.get());
}
JVM參數
-Xmx5m -XX:+PrintGCDetails -Xmn2m -XX:SoftRefLRUPolicyMSPerMB=0
執行結果
[GC (System.gc()) [PSYoungGen: 890K->496K(1536K)] 890K->520K(5632K), 0.0021185 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 496K->0K(1536K)] [ParOldGen: 24K->413K(4096K)] 520K->413K(5632K), [Metaspace: 2685K->2685K(1056768K)], 0.0071067 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]
After new byte[]:Soft Get= null
Object for SoftReference is null
MyObject's finalize called
Heap
PSYoungGen total 1536K, used 71K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 1024K, 6% used [0x00000007bfe00000,0x00000007bfe11e68,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 4096K, used 413K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
object space 4096K, 10% used [0x00000007bfa00000,0x00000007bfa67608,0x00000007bfe00000)
Metaspace used 2693K, capacity 4490K, committed 4864K, reserved 1056768K
class space used 290K, capacity 386K, committed 512K, reserved 1048576K
Process finished with exit code 0
可以發現在手動GC後,軟引用對象已經被回收。此時的軟引用已經與弱引用效果一樣了。下面看弱引用
弱引用
弱引用時一種比軟引用較弱的引用類型。在系統GC時,隻要發現弱引用,不管系統對空間是否足夠,都會對對象進行回收。但是,由于垃圾回收器的線程通常優先級很低,是以,并不一定能很快地發現持有弱引用的對象。在這種情況下,弱引用對象可以存在較長時間。一旦一個弱引用對象被垃圾收集器回收,便會加入導一個注冊引用隊列中
修改軟引用例子中的main方法内容為
public static void main(String[] args) {
//強引用
MyObject object = new MyObject();
//建立引用隊列
ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
WeakReference<MyObject> weakRef = new WeakReference<>(object, queue);
new Thread(new CheckRefQueue(queue)).start();
object = null;
System.out.println("After new byte[]:Soft Get= " + weakRef.get());
System.gc();
System.out.println("After new byte[]:Soft Get= " + weakRef.get());
}
JVM
參數:
-Xmx5m -XX:+PrintGCDetails -Xmn2m
執行結果如下
After new byte[]:Soft Get= I'am MyObject
[GC (System.gc()) [PSYoungGen: 891K->496K(1536K)] 891K->520K(5632K), 0.0016576 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 496K->0K(1536K)] [ParOldGen: 24K->426K(4096K)] 520K->426K(5632K), [Metaspace: 2685K->2685K(1056768K)], 0.0124398 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
MyObject's finalize called
Object for SoftReference is null
After new byte[]:Soft Get= null
Heap
PSYoungGen total 1536K, used 71K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 1024K, 6% used [0x00000007bfe00000,0x00000007bfe11e60,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 4096K, used 426K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
object space 4096K, 10% used [0x00000007bfa00000,0x00000007bfa6a988,0x00000007bfe00000)
Metaspace used 2692K, capacity 4490K, committed 4864K, reserved 1056768K
class space used 290K, capacity 386K, committed 512K, reserved 1048576K
Process finished with exit code 0
通過結果可以看到,在GC之前,弱引用對象并未被垃圾回收器發現,是以通過方法可以獲得對應的強引用,但是隻要進行垃圾回收,弱引用對象一旦被發現,便會立刻被回收,并加入注冊的引用隊列中。此時,再通過
weakRef.get()
方法取得強引用就會失敗
weakRef.get()