基于Java软引用机制最大使用JVM堆内存并杜绝OutOfMemory

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

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

引文: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的内存

    1 //内存监控
    2     public static ReferenceQueue<byte[]> memoryDetectorQueue ;
    3     public static SoftReference<byte[]> memoryDetector;
    4
    5     // initial
    6     public static void initial(){
    7         memoryDetectorQueue = new ReferenceQueue<byte[]>();
    8         memoryDetector = new SoftReference<byte[]>(new byte[(int)(10*1024*1024)],memoryDetectorQueue);
    9     }
  2. 设置一个单独的线程,并在软引用对象初始化后启动该线程,开始监视memoryDetectorQueue是否非空,非空则说明软引用对象由于内存空间不够被清理,内存告急:

     1 public class MemoryMonitorService implements Runnable {
     2
     3     public void run() {
     4         while (true) {
     5             try {
     6                 if (memoryDetectorQueue.remove() != null) {
     7                     doPartClean(); //执行部分缓存的清空以释放内存,可以根据一些LRU算法或按比例来执行清理
     8                 }
     9             } catch (Exception e) {
    10                 logger.error("", e);
    11             }finally{
    12                 memoryDetector = new SoftReference<byte[]>(new byte[(int) (10 * 1024 * 1024)],
    13                         memoryDetectorQueue); // 执行完部分缓存清理后重新创建软引用对象
    14             }
    15         }
    16     }
    17 }

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

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

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

Java 软引用

定义 软引用是使用SoftReference创建的引用,强度弱于强引用,被其引用的对象在内存不足的时候会被回收,不会产生内存溢出. 说明 软引用,顾名思义就是比较"软"一点的引用. 当一个对象与GC Roots之间存在强引用时,无论何时都不会被GC回收掉.如果一个对象与GC Roots之间没有强引用与其关联而存在软引用关联时,那么垃圾回收器对它的态度就取决于内存的紧张程度了.如果内存空间足够,垃圾回收器就不会回收这个对象,但如果内存空间不足了,它就难逃被回收的厄运. 如果一个对象与GC

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

上月,由极客邦.InfoQ和听云联合主办2016 APMCon中国应用性能管理大会圆满落下帷幕.会上,Java冠军Martijn Verburg进行了一场Java and the Machine的分享,讨论了为什么数据分析至关重要.他有着十多年Java经验,目前是创业公司jClarity的CEO,jClarity是一款采用统计和机器学习来探究性能问题根源的方案.会后,InfoQ还专访Martijn以进一步了解沟通. JVM堆内存及一种监测方式 在讨论Martijn的团队如何进行堆内存监测之前,我

[转]JVM 堆内存设置原理

堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大. Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation).年老代和年轻代的划分对垃圾收集影响比较大. 年轻代 所有新生

jvm堆内存优化详解

在日常的运维工作中用到tomcat,都需要对tomcat中的jvm虚拟机进行优化,只有知道需要优化参数的具体用处,才能深刻体会优化jvm的意义所在. 在平常的工作中我们谈对jvm的优化,主要是针对java的堆内存的优化和垃圾回收机制的优化. JVM堆内存示意图: JVM的堆内存的组成: young generation:新生代 eden:伊甸园区 surived:存活区 其中存活区有2个,第1个为S0,第2个为S1 old generation:老年代 permanent generation:

JVM 堆内存设置原理(转)

堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大. Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation).年老代和年轻代的划分对垃圾收集影响比较大. 年轻代 所有新生

【转】JVM 堆内存设置原理

堆内存设置 原理 JVM堆内存分为2块:Permanent Space 和 Heap Space. Permanent 即 持久代(Permanent Generation),主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大. Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation).年老代和年轻代的划分对垃圾收集影响比较大. 年轻代 所有新生

JDK8中JVM堆内存划分

一:JVM中内存 JVM中内存通常划分为两个部分,分别为堆内存与栈内存,栈内存主要用运行线程方法 存放本地暂时变量与线程中方法运行时候须要的引用对象地址. JVM全部的对象信息都 存放在堆内存中.相比栈内存,堆内存能够所大的多,所以JVM一直通过对堆内存划分 不同的功能区块实现对堆内存中对象管理. 堆内存不够最常见的错误就是OOM(OutOfMemoryError) 栈内存溢出最常见的错误就是StackOverflowError.程序有递归调用时候最easy发生 二:堆内存划分 在JDK7以及其

jvm堆内存分布及gc发生的条件

jvm虚拟机对内存管理主要体现在堆内存的管理上,我们可以在启动jvm的时候设置jvm对内存大小及调整策略. 1.jvm启动参数: -Xms:jvm启动时初始堆大小. -Xmx:jvm堆的最大值. -Xss:线程栈大小. -Dname=value:jvm全局属性设置. jvm启动参数设置有很多,以上只是列举本人接触过的几个参数. 1)首先,-Xms是jvm启动时堆内存的初始大小,当堆内存不够用时,jvm调整堆大小到-Xmx设置的大小.一般resin这些服务器会把-Xms和-Xmx大小设置一样以避免

JVM运行时数据区与JVM堆内存模型小结

前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 程序计数器:保存当前线程执行的指令的地址(大意如此). 虚拟机栈:由栈帧组成,而每个栈帧又包括局部变量表.操作数栈.动态连接(调用其他方法).出口(被调用时返回值) -- 每个栈帧就代表了一个方法的执行. 本地方法栈:类似虚拟机栈,只不过方法改成了native方法. 方法区:保存了类的各种信息.类的