题记:说好的坚持一周两篇文章在无数琐事和自己的懒惰下没有做好,在此表达一下对自己的不满并对有严格执行力的人深表敬意!!!!

---------------------------------------------------------------------------------------------------------------------------------

引文:Java程序员对OutOfMemory并不陌生,一般来说,出现此异常主要是由于应用里缓存了大量的数据没有被GC掉导致堆内存溢出,可是很多时候,为了减少重复计算或提升运行速度,必需要将一些数据缓存起来,比如启动的时候加载配置文件信息、从数据库里初始化进来的信息、运行过程中得到的一些中间结果等。程序员往JVM里加载这些数据的时候往往会很纠结,一方面想缓存的越多越好,尽量减少查库和重复计算,但另一方面过多的缓存对GC造成压力,甚至要提心吊胆的考虑溢出的问题。

需求:如果能有一种方法可以尽可能的缓存数据提高运行效率,又可以在GC前主动清空一部分过期数据从而防止内存溢出,该有多好。下面,我要讲的基于Java软引用实现堆内存监控,是笔者亲身在生产系统的实践,或许可以帮助程序员在这方面做一些尝试。

导读:文章会先解释什么是软引用,接着会说明GC对软引用的处理特点,围绕其特点利用JDK自带的相关类阐述代码实现细节。

正文:

1.  什么是软引用:

我们知道,Java中有四种引用关系,分别是强引用、软引用、弱引用、虚引用,如下图:

强引用:指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.  实现细节

根据上面的分析和实际的开发实践,利用软引用对象监控虚拟机内存使用情况的代码实现如下:

  1. 初始化软引用对象与引用队列,并设置软引用对象占用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);
    }
  2. 设置一个单独的线程,并在软引用对象初始化后启动该线程,开始监视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); // 执行完部分缓存清理后重新创建软引用对象
    }
    }
    }
    }

    说明:memoryDetectorQueue.remove()方法会一直等待,阻塞到某个对象变得可用为止,它返回的值不为空时说明memoryDetector 软引用对象被GC掉了。

  3. 调用new MemoryMonitorService().start()启动监控线程。一般来说,上面代码里的doPartClean()工作是由专门的清理类来辅助的。

基于Java软引用机制最大使用JVM堆内存并杜绝OutOfMemory的更多相关文章

  1. 巩固java(二)----JVM堆内存结构及垃圾回收机制

    前言:        我们在运行程序时,有时会碰到内存溢出(OutOfMemoryError)的问题,为了解决这种问题,我们有必要了解JVM的内存结构和垃圾回收机制. 正文: 1.JVM堆内存结构   ...

  2. 【高频Java面试题】简单说说JVM堆的内存结构和GC回收流程

    目录 前言 JVM堆内存结构简述 JVM堆内存结构图 堆初体验 结构详情 新生代 老年代 永久代/元空间 GC回收流程 GC回收流程图 GC回收详细流程 查看JDK自带可视化堆空间图 总结 前言 我们 ...

  3. JVM堆内存监测的一种方式,性能调优依旧任重道远

    上月,由极客邦.InfoQ和听云联合主办2016 APMCon中国应用性能管理大会圆满落下帷幕.会上,Java冠军Martijn Verburg进行了一场Java and the Machine的分享 ...

  4. 【转】JVM 堆内存设置原理

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  5. [转]JVM 堆内存设置原理

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  6. JVM 堆内存设置原理

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  7. JVM 堆内存设置原理(转)

    堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定 ...

  8. JVM堆内存设置

    今天碰到了一个题目,讲的是关于堆内存的问题,题目如下   下面哪种情况会导致持久区jvm堆内存溢出? A.循环上万次的字符串处理 B.在一段代码内申请上百M甚至上G的内存 C.使用CGLib技术直接操 ...

  9. JDK8中JVM堆内存划分

    一:JVM中内存 JVM中内存通常划分为两个部分,分别为堆内存与栈内存,栈内存主要用运行线程方法 存放本地暂时变量与线程中方法运行时候须要的引用对象地址. JVM全部的对象信息都 存放在堆内存中.相比 ...

随机推荐

  1. VUE+WebPack前端游戏设计:实现物体的拖拽动态特效

  2. Professional C# 6 and .NET Core 1.0 - Chapter 43 WebHooks and SignalR

    本文内容为转载,重新排版以供学习研究.如有侵权,请联系作者删除. 转载请注明本文出处:Professional C# 6 and .NET Core 1.0 - Chapter 43 WebHooks ...

  3. 微信小程序(一)基本知识初识别

    最近微信圈里小程序很火的样子,以前小程序刚开始的时候研究了一下,多日没关注发现一些东西都淡忘了,最后决定还是记录下来的好.    毕竟好记星比不上烂笔头嘛~

  4. highchart 曲线图

    $(function() { Highcharts.setOptions({ global: { useUTC: false } }); var chart = new Highcharts.Char ...

  5. [SoapUI] 如何让某个步骤的Assertion失败之后继续执行后面的步骤

    To continue tests executing after failed test step you need disable "Abort on error" optio ...

  6. Greeplum 系列(四) 数据的装载与卸裁

    Greeplum 系列(四) 数据的装载与卸裁 装载数据有以下种方法: insert copy 外部表 gpload 下面以 member_delta 表为例分别介绍这四种方法. create tab ...

  7. Oracle——约束

    NOT NULLUNIQUE PRIMARY KEYFOREIGN KEYCHECK 如果不指定约束名 ,Oracle server 自动按照 SYS_Cn 的格式指定约束名 --指定约束名 CREA ...

  8. IE6,7,8在boostrap中兼容h5和css3

    IE6.7.8版本(IE9以下版本)浏览器兼容html5新增的标签,引入下面代码文件即可: <script src="https://oss.maxcdn.com/libs/html5 ...

  9. C++11学习笔记之三lamda表达式,std::function, std::bind

    //lamda //first lamda [] {}; // second lamda []() //or no need () when paramater is null { std::cout ...

  10. Linq学习<五> 运用linq查询Xml

    这节将学习如何用 linq查询xml 一.我们先看看在xml中我们怎么操作 public void xmlWayToQueryXmlFile() { XmlDocument xmldoc = new ...