基于Java軟引用機制最大使用JVM堆內存并杜絕OutOfMemory

jopen 9年前發布 | 18K 次閱讀 JVM Java開發

引文:Java程序員對OutOfMemory并不陌生,一般來說,出現此異常主要是由于應用里緩存了大量的數據沒有被GC掉導致堆內存溢出,可是 很多時候,為了減少重復計算或提升運行速度,必需要將一些數據緩存起來,比如啟動的時候加載配置文件信息、從數據庫里初始化進來的信息、運行過程中得到的 一些中間結果等。程序員往JVM里加載這些數據的時候往往會很糾結,一方面想緩存的越多越好,盡量減少查庫和重復計算,但另一方面過多的緩存對GC造成壓 力,甚至要提心吊膽的考慮溢出的問題。

需求:如果能有一種方法可以盡可能的緩存數據提高運行效率,又可以在GC前主動清空一部分過期數據從而防止內存溢出,該有多好。下面,我要講的基于Java軟引用實現堆內存監控,是筆者親身在生產系統的實踐,或許可以幫助程序員在這方面做一些嘗試。

導讀:文章會先解釋什么是軟引用,接著會說明GC對軟引用的處理特點,圍繞其特點利用JDK自帶的相關類闡述代碼實現細節。

正文:

1.        什么是軟引用:

我們知道,Java中有四種引用關系,分別是強引用、軟引用、弱引用、虛引用,如下圖:

基于Java軟引用機制最大使用JVM堆內存并杜絕OutOfMemory

強引用:指JVM內存管理器從根引用集合(ROOt Set)出發遍尋堆中所有可到達的對象引用關系,也是常用的引用類型,如Object obj = new Object();只要強引用存在則GC時則必定不被回收。

軟引用:用來描述一些有用但并不是必需的對象,在Java中用java.lang.ref.SoftReference類來表示。如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。

弱引用:用來描述非必需對象的,在java中,用java.lang.ref.WeakReference類來表示。當JVM進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象。

虛引用:在任何時候都可能被垃圾回收器回收的對象應用,用java.lang.ref.PhantomReference類表示。如果一個對象與虛引用關聯,則跟沒有引用與之關聯一樣,此引用關系更多的是與虛引用隊列相關聯以方便做一些GC監控。

2.        軟引用特點

根據java幫助文檔:"軟引用對象在響應內存需要時,由垃圾回收器決定是否清除此 對象。軟引用對象最常用于實現內存敏感的緩存。假定垃圾回收器確定在某一時間點某個對象是軟可到達對象。這時,它可以選擇自動清除針對該對象的所有軟引 用,以及通過強引用鏈,從其可以到達該對象的針對任何其他軟可到達對象的所有軟引用。在同一時間或晚些時候,它會將那些已經向引用隊列注冊的新清除的軟引 用加入隊列。軟可到達對象的所有軟引用都要保證在虛擬機拋出 OutOfMemoryError 之前已經被清除。否 則,清除軟引用的時間或者清除不同對象的一組此類引用的順序將不受任何約束。然而,虛擬機實現不鼓勵清除最近訪問或使用過的軟引用。此類的直接實例可用于 實現簡單緩存;該類或其派生的子類還可用于更大型的數據結構,以實現更復雜的緩存。只要軟引用的指示對象是強可到達對象,即正在實際使用的對象,就不會清 除軟引用。例如,通過保持最近使用的項的強指示對象,并由垃圾回收器決定是否放棄剩余的項,復雜的緩存可以防止放棄最近使用的項。"

根據上述內容我們知道,軟引用對象會在OutOfMemoryError 之前由JVM保證將其回收,并把它加入到其注冊的清除隊列中。因此,通過監控該隊列是否有即將被清除的軟引用對象,我們就可以間接得知java應用是否已 經到溢出崩潰邊緣了,并在其溢出前迅速執行部分緩存數據的清空工作從而讓虛擬機可以清理出一些內存出來避免堆內存的溢出,更進一步想,我們可以將該軟引用 對象設置成占一定內存大小的對象,如10M,這樣當虛擬機內存不足時會第一時間將此對象回收進而騰出10M空余內存,進而緩解內存不足,同時為應用爭取了 寶貴清空部分緩存數據的時間,有效避免直接拋出內存溢出的異常。

3.        實現細節

根據上面的分析和實際的開發實踐,利用軟引用對象監控虛擬機內存使用情況的代碼實現如下:

初始化軟引用對象與引用隊列,并設置軟引用對象占用10M的內存
//內存監控
    public static ReferenceQueue<byte[]> memoryDetectorQueue ;
public static SoftReference<byte[]> memoryDetector;

// initial 
public static void initial(){        
    memoryDetectorQueue = new ReferenceQueue<byte[]>();
    memoryDetector = new SoftReference<byte[]>(new byte[(int)(10*1024*1024)],memoryDetectorQueue);
}</pre> <br />

設置一個單獨的線程,并在軟引用對象初始化后啟動該線程,開始監視memoryDetectorQueue是否非空,非空則說明軟引用對象由于內存空間不夠被清理,內存告急:

public class MemoryMonitorService implements Runnable {

public void run() {
    while (true) {
        try {
            if (memoryDetectorQueue.remove() != null) {
                doPartClean(); //執行部分緩存的清空以釋放內存,可以根據一些LRU算法或按比例來執行清理
            }
        } catch (Exception e) {
            logger.error("", e);
        }finally{
            memoryDetector = new SoftReference<byte[]>(new byte[(int) (10 * 1024 * 1024)],
                    memoryDetectorQueue); // 執行完部分緩存清理后重新創建軟引用對象
        }
    }
}

}</pre>

  1. 說明:memoryDetectorQueue.remove()方法會一直等待,阻塞到某個對象變得可用為止,它返回的值不為空時說明memoryDetector 軟引用對象被GC掉了。

  2. 調用new MemoryMonitorService().start()啟動監控線程。一般來說,上面代碼里的doPartClean()工作是由專門的清理類來輔助的。
來自:http://www.cnblogs.com/dimmacro/p/4506714.html?utm_source=tuicool

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!