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

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

引文: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. java常用正则校验工具类

    正则常用校验工具类 import java.util.regex.Pattern; /** * @program: * @description: 校验工具类 * @author: xujingyan ...

  2. Ros学习——Cmakelists.txt文件解读

    1.过程 .Required CMake Version (cmake_minimum_required) //CMake 需要的版本 .Package Name (project()) //#定义工 ...

  3. c++ 中介者模式(mediator)

    中介者模式:用一个中介对象来封装一系列的对象交互.中介者使各个对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互.中介者模式的例子很多,大到联合国安理会,小到房屋中介.下面以 ...

  4. IDEA04 工具窗口管理、各种跳转、高效定位、行操作、列操作、live template、postfix、alt enter、重构、git使用

    1 工具窗口管理 所有的窗口都是在view -> tools windows 下面的,这些窗口可以放在IDEA的上下左右各个位置:右键某个窗口后选择move to 即可进行位置调整 2 跳转 2 ...

  5. git 常用commands(转)

    常用 Git 命令清单   作者: 阮一峰 日期: 2015年12月 9日 我每天使用 Git ,但是很多命令记不住. 一般来说,日常使用只要记住下图6个命令,就可以了.但是熟练使用,恐怕要记住60- ...

  6. maven filter插件只替换了部分变量问题

    maven filter简介 maven的resources插件,有一个filter的作用,能够在打包的时候,从特定文件里读取key-value对,替换配置文件中的占位符变量.很多线上线下有不同环境的 ...

  7. Git回滚到历史节点(SourceTree篇)

    转自:http://blog.csdn.net/u010416101/article/details/78142697.https://www.zhihu.com/question/48178380 ...

  8. 计蒜客D2T2 蒜头君的排序(动态维护树状数组)

    蒜头君的排序(sort) 2000ms 262144K 蒜头君是一个爱思考的好孩子,这一天他学习了冒泡排序,于是他就想,把一个乱序排列通过冒泡排序排至升序需要多少次交换,这当然难不倒他,于是他想来点刺 ...

  9. [Email] 收发邮件的协议 : IMAP and SMTP , POP3 and SMTP

    支持 IMAP 和 SMTP 的应用 与仅同步收件箱的 POP 不同,IMAP 同步所有电子邮件文件夹. 在电子邮件应用中使用以下设置. 接收 (IMAP) 服务器 服务器地址:imap-mail.o ...

  10. .NET开源MSSQL、Redis监控产品Opserver之安全配置

    简介 Opserver是Stack Overflow的开源监控解决方案,由Stack Exchange发布,基于.NET框架构建.开源地址:https://github.com/opserver/Op ...