詳解JAVA 弱引用
定義
弱引用是使用WeakReference創(chuàng)建的引用,弱引用也是用來描述非必需對象的,它是比軟引用更弱的引用類型。在發(fā)生GC時,只要發(fā)現(xiàn)弱引用,不管系統(tǒng)堆空間是否足夠,都會將對象進(jìn)行回收。
說明
弱引用,從名字來看就很弱嘛,這種引用指向的對象,一旦在GC時被掃描到,就逃脫不了被回收的命運。
但是,弱引用指向的對象也并不一定就馬上會被回收,如果弱引用對象較大,直接進(jìn)到了老年代,那么就可以茍且偷生到Full GC觸發(fā)前,所以弱引用對象也可能存在較長的一段時間。一旦一個弱引用對象被垃圾回收器回收,便會加入到一個引用隊列中(如果有的話)。
弱引用對應(yīng)的類為WeakReference,舉個栗子:
String s = new String("Frank");
WeakReference<String> weakRef = new WeakReference<String>(s);
s = null;
這里我們把s設(shè)置為null后,字符串對象便只有弱引用指向它。
弱可達(dá)
如果一個對象與GC Roots之間僅存在弱引用,則稱這個對象為弱可達(dá)(weakly reachable)對象。
注意
在垃圾回收器回收一個對象前,WeakReference類所提供的get方法會返回其引用對象的強(qiáng)引用,一旦垃圾回收器回收掉該對象之后,get方法將返回null。所以在獲取弱引用對象的代碼中,一定要判斷是否為null,以免出現(xiàn)NullPointerException異常導(dǎo)致應(yīng)用崩潰。
下面的代碼會讓s再次持有對象的強(qiáng)引用:
s = weakRef.get();
如果在weakRef包裹的對象被回收前,用強(qiáng)引用關(guān)聯(lián)該對象,那這個對象又會變成強(qiáng)可達(dá)狀態(tài)。
來看一個簡單的栗子了解一下WeakReference引用的對象是何時被回收的:
public class WeakReferenceTest {
private static final List<Object> TEST_DATA = new LinkedList<>();
private static final ReferenceQueue<TestClass> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
TestClass obj = new TestClass("Test");
WeakReference<TestClass> weakRef = new WeakReference<>(obj, QUEUE);
//可以重新獲得OOMClass對象,并用一個強(qiáng)引用指向它
//oomObj = weakRef.get();
// 該線程不斷讀取這個弱引用,并不斷往列表里插入數(shù)據(jù),以促使系統(tǒng)早點進(jìn)行GC
new Thread(() -> {
while (true) {
TEST_DATA.add(new byte[1024 * 100]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(weakRef.get());
}
}).start();
// 這個線程不斷讀取引用隊列,當(dāng)弱引用指向的對象唄回收時,該引用就會被加入到引用隊列中
new Thread(() -> {
while (true) {
Reference<? extends TestClass> poll = QUEUE.poll();
if (poll != null) {
System.out.println("--- 弱引用對象被jvm回收了 ---- " + poll);
System.out.println("--- 回收對象 ---- " + poll.get());
}
}
}).start();
//將強(qiáng)引用指向空指針 那么此時只有一個弱引用指向TestClass對象
obj = null;
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
}
static class TestClass {
private String name;
public TestClass(String name) {
this.name = name;
}
@Override
public String toString() {
return "TestClass - " + name;
}
}
}
設(shè)置一下虛擬機(jī)參數(shù):
-verbose:gc -Xms4m -Xmx4m -Xmn2m
運行結(jié)果如下:
[GC (Allocation Failure) 1017K->464K(3584K), 0.0014345 secs]
[GC (Allocation Failure) 1483K->536K(3584K), 0.0017221 secs]
[GC (Allocation Failure) 1560K->648K(3584K), 0.0036572 secs]
TestClass - Test
TestClass - Test
TestClass - Test
[GC (Allocation Failure) 1621K->984K(3584K), 0.0011455 secs]
--- 弱引用對象被jvm回收了 ---- java.lang.ref.WeakReference@51a947fe
--- 回收對象 ---- null
null
...省略n個null和幾次GC信息
[Full GC (Ergonomics) 2964K->2964K(3584K), 0.0025450 secs]
[Full GC (Allocation Failure) 2964K->2964K(3584K), 0.0021907 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid6860.hprof ...
Heap dump file created [3912229 bytes in 0.011 secs]
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at weakhashmap.WeakReferenceTest.lambda$main$0(WeakReferenceTest.java:22)
at weakhashmap.WeakReferenceTest$$Lambda$1/764977973.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
可以看到,其實弱引用也并不是一發(fā)生GC就被回收掉了。
應(yīng)用場景
如果一個對象僅僅是偶爾使用,并且希望在使用時隨時就能獲取到,但又不想影響此對象的垃圾收集,那么你應(yīng)該用 WeakReference 來引用該對象。
弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機(jī)就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中。
一般來說,很少直接使用WeakReference,而是使用WeakHashMap。在WeakHashMap中,內(nèi)部有一個引用隊列,插入的元素會被包裹成WeakReference,并加入隊列中,用來做緩存再合適不過。
在Tomcat的緩存中,其實就用到了WeakHashMap:
public final class ConcurrentCache<K,V> {
private final int size;
private final Map<K,V> eden;
private final Map<K,V> longterm;
public ConcurrentCache(int size) {
this.size = size;
this.eden = new ConcurrentHashMap<>(size);
this.longterm = new WeakHashMap<>(size);
}
public V get(K k) {
// 先從eden中取
V v = this.eden.get(k);
if (v == null) {
// 如果取不到再從longterm中取
synchronized (longterm) {
v = this.longterm.get(k);
}
// 如果取到則重新放到eden中
if (v != null) {
this.eden.put(k, v);
}
}
return v;
}
public void put(K k, V v) {
if (this.eden.size() >= size) {
// 如果eden中的元素數(shù)量大于指定容量,將所有元素放到longterm中
synchronized (longterm) {
this.longterm.putAll(this.eden);
}
this.eden.clear();
}
this.eden.put(k, v);
}
}
這里有eden和longterm的兩個map,如果對jvm堆了解的話,可以看出tomcat在這里是使用ConcurrentHashMap和WeakHashMap做了類似分代緩存的操作。
在put方法里,在插入鍵值對時,先檢查eden緩存的容量是否超出設(shè)定的大小。如果沒有則直接放入eden緩存,如果超了則鎖定longterm將eden中所有的鍵值對都放入longterm。再將eden清空并插入該鍵值對。
在get方法中,也是優(yōu)先從eden中找對應(yīng)的key,如果沒有則進(jìn)入longterm緩存中查找,找到后就加入eden緩存并返回。
經(jīng)過這樣的設(shè)計,相對常用的對象都能在eden緩存中找到,不常用(有可能被銷毀的對象)的則進(jìn)入longterm緩存。而longterm的key的實際對象沒有其他引用指向它時,gc就會自動回收heap中該弱引用指向的實際對象,并將弱引用放入其引用隊列中。
弱引用與軟引用對比
弱引用與軟引用的區(qū)別在于:
- 只具有弱引用的對象擁有更短暫的生命周期。
- 被垃圾回收器回收的時機(jī)不一樣,在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。而被軟引用關(guān)聯(lián)的對象只有在內(nèi)存不足時才會被回收。
- 弱引用不會影響GC,而軟引用會一定程度上對GC造成影響。
相似之處:都是用來描述非必需對象的。
那么什么時候用SoftReference,什么時候用WeakReference呢?
如果緩存的對象是比較大的對象,使用頻率相對較高的對象,那么使用SoftReference會更好,因為這樣能讓緩存對象有更長的生命周期。
如果緩存對象都是比較小的對象,使用頻率一般或者相對較低,那么使用WeakReference會更合適。
當(dāng)然,如果實在不知道選哪個,一般而言,用作緩存時使用WeakHashMap都不會有太大問題。
小結(jié)
- 弱引用是比軟引用更弱的引用類型
- 弱引用不能延長對象的生命周期,一旦對象只剩下弱引用,它就隨時可能會被回收
- 可以通過弱引用獲取對象的強(qiáng)引用
- 弱引用適合用作緩存
以上就是詳解 JAVA 弱引用的詳細(xì)內(nèi)容,更多關(guān)于java 弱引用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于Java中增強(qiáng)for循環(huán)使用的注意事項
for循環(huán)語句是java循環(huán)語句中最常用的循環(huán)語句,一般用在循環(huán)次數(shù)已知的情況下使用,這篇文章主要給大家介紹了關(guān)于Java中增強(qiáng)for循環(huán)使用的注意事項,需要的朋友可以參考下2021-06-06
Spring?Cloud?整合?nacos實現(xiàn)動態(tài)配置中心的詳細(xì)步驟
這篇文章主要介紹了Spring?Cloud?整合?nacos?實現(xiàn)動態(tài)配置中心,整合步驟是通過添加依賴新建nacos配置,本文分步驟通過實例代碼給大家詳細(xì)講解,需要的朋友可以參考下2022-10-10
Java你不了解的大數(shù)型BigInteger與BigDecimal類
這篇文章主要介紹了Java 處理超大數(shù)類型之BigInteger與BigDecimal案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2022-05-05
SpringBoot+React實現(xiàn)計算個人所得稅
本文將以個人所得稅的計算為例,使用React+SpringBoot+GcExcel來實現(xiàn)這一功能,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以了解下2023-09-09
SpringBoot中DTO/VO/Entity相互轉(zhuǎn)換詳解
在我們平時開發(fā)中,dto、vo、entity之間的相互轉(zhuǎn)換是很頻繁的操作,這篇文章就簡單記錄一下在平時開發(fā)中SpringBoot的轉(zhuǎn)換方法,希望對大家有所幫助2025-01-01
Spring Boot集成Seata實現(xiàn)基于AT模式的分布式事務(wù)的解決方案
Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡單易用的分布式事務(wù)服務(wù),這篇文章主要介紹了Spring Boot集成Seata實現(xiàn)基于AT模式的分布式事務(wù),需要的朋友可以參考下2024-08-08

