Java中的 "弱" 引用有啥用?

2021-01-09 51CTO

Java裡一個對象obj被創建時,被放在堆裡。當GC運行的時候,發現沒有任何引用指向obj,那麼就會回收obj對象的堆內存空間。

換句話說,一個對象被回收, 必須滿足兩個條件:

(1)沒有任何引用指向它

(2)GC被運行。

在實際開發中,我們可以通過把所有指向某個對象的referece置空來保證這個對象在下次GC運行的時候被回收,類似下面:

Object c = new Car();      c=null; 

但是,這樣做是一件很繁瑣並且違背GC自動回收原則的事。對於簡單的情況, 手動置空是不需要程式設計師來做的, 因為在java中, 對於簡單對象, 當調用它的方法執行完畢後, 指向它的引用會被從棧中彈出, 所以它就能在下一次GC執行時被回收了。

但是, 也有特殊例外. 當使用cache的時候, 由於cache的對象正是程序運行需要的, 那麼只要程序正在運行, cache中的引用就不會被GC(或者說, cache中的reference擁有了和主程序一樣的life cycle). 那麼隨著cache中的reference越來越多, GC無法回收的object也越來越多, 無法被自動回收。當這些object需要被回收時, 回收這些object的任務只有交給程序編寫者了。然而這卻違背了GC的本質(自動回收可以回收的objects)。

所以, java中引入了weak reference。

Object c = new Car(); //只要c還指向car object, car object就不會被回收 -->(強引用) 

當一個對象僅僅被weak reference指向, 而沒有任何其他strong reference指向的時候, 如果GC運行, 那麼這個對象就會被回收。另外,關注公眾號Java技術棧,在後臺回覆:面試,可以獲取我整理的 JVM 系列面試題和答案,非常齊全。

下面這個是網上的例子,首先定義一個實體類:

public class Car {      private double     price;      private String    color;      public Car(double price, String color)      {          this.price = price;          this.color = color;      }     public double getPrice()      {          return price;      }      public String getColor()      {          return color;      }      public String toString()      {          return "This car is a " + this.color + " car, costs $" + price;      }  } 

一般使用WeakReference的時候都會定義一個類繼承自WeakReference,在這個類中再定義一些別的屬性,這裡就不定義別的屬性了:

public class WeakReferenceCar extends WeakReference<Car>  {      public WeakReferenceCar(Car car)      {          super(car);      }  } 

main函數調用一下,當然為了更清楚地看到GC的效果,設置虛擬機參數」-XX:+PrintGCDetails」:

public static void main(String[] args)  {      Car car = new Car(2000.0, "red");      WeakReferenceCar wrc = new WeakReferenceCar(car);      wrc.setStr("111");      int i = 0;      while (true)      {          if (wrc.get() != null)          {              i++;              System.out.println("WeakReferenceCar's Car is alive for " + i + ", loop - " + wrc);          }          else          {              System.out.println("WeakReferenceCar's Car has bean collected");              break;          }      }  } 

最後是運行結果:

WeakReferenceCar's Car is alive for 68450, loop - interview.WeakReferenceCar@776ec8df  WeakReferenceCar's Car is alive for 68451, loop - interview.WeakReferenceCar@776ec8df  WeakReferenceCar's Car is alive for 68452, loop - interview.WeakReferenceCar@776ec8df  WeakReferenceCar's Car is alive for 68453, loop - interview.WeakReferenceCar@776ec8df  [GC (Allocation Failure) [PSYoungGen: 34304K->1000K(38400K)] 34320K->1016K(125952K), 0.0015129 secs] [Times: user=0.02 sys=0.02, real=0.00 secs]   WeakReferenceCar's Car is alive for 68454, loop - interview.WeakReferenceCar@776ec8df  WeakReferenceCar's Car has bean collected  Heap  PSYoungGen      total 38400K, used 1986K [0x00000000d5e00000, 0x00000000da900000, 0x0000000100000000)  eden space 33280K, 2% used [0x00000000d5e00000,0x00000000d5ef6b70,0x00000000d7e80000)  from space 5120K, 19% used [0x00000000d7e80000,0x00000000d7f7a020,0x00000000d8380000)  to   space 5120K, 0% used [0x00000000da400000,0x00000000da400000,0x00000000da900000)  ParOldGen       total 87552K, used 16K [0x0000000081a00000, 0x0000000086f80000, 0x00000000d5e00000)  object space 87552K, 0% used [0x0000000081a00000,0x0000000081a04000,0x0000000086f80000)  Metaspace       used 3547K, capacity 4564K, committed 4864K, reserved 1056768K  class space    used 381K, capacity 388K, committed 512K, reserved 1048576K 

可以看到在68454循環之後,WeakReferenceCar關聯的對象Car被回收掉了,注意是弱引用關聯的對象car被回收,而不是弱引用本身wrc被回收。

WeakReference的一個特點是它何時被回收是不可確定的, 因為這是由GC運行的不確定性所確定的. 所以, 一般用weak reference引用的對象是有價值被cache, 而且很容易被重新被構建, 且很消耗內存的對象.

在weak reference指向的對象被回收後, weak reference本身其實也就沒有用了. java提供了一個ReferenceQueue來保存這些所指向的對象已經被回收的reference. 用法是在定義WeakReference的時候將一個ReferenceQueue的對象作為參數傳入構造函數。

【責任編輯:

龐桂玉

TEL:(010)68476606】

點讚 0

相關焦點

  • 引用數據類型的概念_引用數據類型有哪幾種
    在上述引用中,&是「引用聲明符」,並不代表地址。   不要理解為「把a的值賦給b的地址」。引用類型的數據存儲在內存的堆中,而內存單元中只存放堆中對象的地址。聲明引用並不開闢內存單元,b和a都代表同一變量單元。   注意:在聲明引用變量類型時,必須同時使之初始化,即聲明它代表哪一變量。
  • 用經典案例來幫助初學者解析Java的「多態」
    我這裡還是用我13年前給我們公司新員工做內部培訓時用到的看起來似乎有點老掉牙的、但是仍然十分經典的案例來重新給有需要的java愛好者呈現一下「多態」的奧秘所在!3.實現接口Shape.java的實現類之一 Rectangle.java 即矩形類:如上,Rectangle.java類實現了接口Shape.java中的若干抽象方法,當然,若Shape.java是父類或抽象類的話,Rectangle.java須重寫父類的getArea()方法、getPer(
  • 深入分析java中的多態(從jvm角度分析)
    對於java中多態概念的理解一直是面試常問的問題,所以今天花了一些時間好好地整理了一下,力求從java虛擬機的角度來分析和理解多態。一、認識多態1、方法調用在Java中,方法調用有兩類,動態方法調用與靜態方法調用。
  • Java程式設計師必備基礎:Java代碼是怎麼運行的?
    最近複習了深入理解Java虛擬機,做了一下總結,希望對大家有幫助,如果有不正確的地方,歡迎提出,感激不盡。 符號引用驗證:發生在虛擬機將符號引用轉化為直接引用的時候,如:校驗符號引用中通過字符串描述的全限定名是否能找到對應的類。 準備 準備階段是正式為類變量分配內存並設置類變量初始值,這些變量所使用的內存都將在方法區中進行分配。
  • Java中Lambda表達式的5種不同語法
    用括號括起來的形式參數的逗號分隔列表。在這種情況下(String m, String n)2. 箭頭標記 ->3. 主體,由單個表達式或語句塊組成。如果可以從上下文中推斷出參數的參數類型,則可以省略該類型。
  • 值傳遞和引用傳遞傳的到底是啥?
    傳值還是傳引用可能在 Java、Python 這種語言中常常會困擾一些初學者,但是如果你有 C/C++背景的話,那這個理解起來就是 so easy。今天我就從 C 語言出發,一次性把 Java、Python 這些都給大家講明白。
  • 一文搞懂WeakHashMap工作原理(java後端面試高薪必備知識點)
    1、四種引用在jvm中,一個對象如果不再被使用就會被當做垃圾給回收掉,判斷一個對象是否是垃圾,通常有兩種方法:引用計數法和可達性分析法。不管是哪一種方法判斷一個對象是否是垃圾的條件總是一個對象的引用是都沒有了。
  • Java編程中基礎反射詳細解析
    類的連接又分為下面三個階段:驗證:確保被加載類的正確性準備:負責為類的靜態成員分配內存,並設置默認初始化值解析:將類中的符號引用替換為直接引用1.3 類的初始化類的初始化在java中對類變量指定初始值得方法有兩種:1.
  • 萬字梳理,帶你拿下 Java 面試題!
    圖中用紅色標明的區域表示對象處於強可達階段。JDK1.2 介紹了 java.lang.ref 包,對象的生命周期有四個階段:強可達(Strongly Reachable)、軟可達(Soft Reachable)、弱可達(Weak Reachable)、 幻象可達(Phantom Reachable)。
  • 2020年Java基礎高頻面試題匯總
    [9、 Hashcode的作用java的集合有兩類,一類是List,還有一類是Set。前者有序可重複,後者無序不重複。當我們在set中插入的時候怎麼判斷是否已經存在該元素呢,可以通過equals方法。但是如果元素太多,用這樣的方法就會比較滿。
  • Java基礎學習:java中的基本數據類型
    一、基本類型 1、基本類型 不使用New創建,聲明一個非引用傳遞的變量,且變量的值直接置於堆棧中,大小不隨運行環境變化,效率更高。使用new創建的引用對象存儲在堆中。
  • Java8 lambda表達式語法
    但是有一點這裡強調一下(Windows系統):目前我們工作的版本一般是java 6或者java 7,所以很多人安裝java8基本都是學習為主。這樣就在自己的機器上會存在多版本的JDK。而且大家一般是希望在命令行中執行java命令是基於老版本的jdk。但是在安裝完jdk8並且沒有設置path的情況下,你如果在命令行中輸入:java -version,屏幕上會顯示是jdk 8。
  • Java常見內存溢出異常分析
    space)   我們知道Hotspot jvm通過持久帶實現了Java虛擬機規範中的方法區,而運行時的常量池就是保存在方法區中的,因此持久帶溢出有可能是運行時常量池溢出,也有可能是方法區中保存的class對象沒有被及時回收掉或者class信息佔用的內存超過了我們配置。
  • Java8 lambda表達式
    在java8中,這種限制有所放鬆,可以引用非final的參數,雖然沒有申明為final類型,但是必須當做final類型看待,不能去改變其所指的對象,否則會報錯。如果多次為一個變量賦值,並且單算在lambda表達式中進行引用,那麼會得到一個變異錯誤。
  • JAVA反序列化—FastJson抗爭的一生
    並沒有調用is開頭的方法自己從源碼中分析或者嘗試在類中添加isXx方法都是不會被調用的,這裡只是為了指出其他文章中的一個錯誤。這個與調用的set方法綁定的屬性,再之後並沒有發現對於調用過程有什麼影響。所以只要目標類中有滿足條件的set方法,然後得到的方法變量名存在於序列化字符串中,這個set方法就可以被調用。
  • java基礎教程:Collection集合,Collection 常用API
    集合:集合是java中提供的一種容器,可以用來存儲多個數據。集合和數組既然都是容器,它們有什麼區別呢?數組的長度是固定的。集合的長度是可變的。數組中存儲的是同一類型的元素,可以存儲任意類型數據。集合存儲的都是引用數據類型。如果想存儲基本類型數據需要存儲對應的包裝類型。
  • 詳細介紹Java多態性(二)
    通過將子類對象引用賦值給超類對象引用變量來實現動態方法調用。java 的這種機制遵循一個原則:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。1. 如果a是類A的一個引用,那麼,a可以指向類A的一個實例,或者說指向類A的一個子類。2.
  • Java類隔離加載實現原理是什麼?
    然而JVM會選擇當前類的類加載器來加載所有該類的引用的類。類隔離技術是什麼?到了運行的時候,默認情況下一個項目的所有類都是用同一個類加載器加載的,所以不管你依賴了多少個版本的 C,最終只會有一個版本的 C 被加載到 JVM 中。當 B 要去訪問 Log.error,就會發現 Log 壓根就沒有 error 方法,然後就拋異常java.lang.NoSuchMethodError。這就是類衝突的一個典型案例。
  • 萬字概覽 Java 虛擬機
    在 JVM 中,一個方法從調用開始到調用結束就是一個棧幀入棧和出棧的過程。每個線程棧的大小使用參數 -Xss 進行指定,默認是 1M。絕大部分情況下用不了這麼大,建議配置為 256kb 即可。棧空間越大,則 JVM 能容納的線程數越少;棧空間越小,可遞歸深度越低。棧幀包含有局部變量表,存儲編譯期可知的基本數據類型以及對象的符號引用等信息,還包含有操作數棧、動態連結、方法出口等信息。
  • Java-類型轉換,使用強制向下轉型解決多態的弊端
    瀏覽器版本過低,暫不支持視頻播放1.1.java的類型轉換1.1.1.java的類型轉換自動向上轉型: 可以直接將子類型引用賦值給父類型變量,可以自動進行,叫做自動向上轉型;例如:class Fu{}class Zi extends Fu{}