首页 > 编程语言 >Java 如何有效地避免OOM:善于利用软引用和弱引用

Java 如何有效地避免OOM:善于利用软引用和弱引用

时间:2022-12-15 10:37:17浏览次数:73  
标签:Java OOM 回收 对象 引用 JVM new public


  想必很多朋友对OOM(OutOfMemory)这个错误不会陌生,而当遇到这种错误如何有效地解决这个问题呢?今天我们就来说一下如何利用软引用和弱引用来有效地解决程序中出现的OOM问题。下面是本文的目录大纲:

  一.了解 强引用、软引用、弱引用、虚引用的概念

  二.进一步理解软引用和弱引用

  三.如何利用软引用和弱引用解决OOM问题

一.了解 强引用、软引用、弱引用、虚引用的概念

  在Java中,虽然不需要程序员手动去管理对象的生命周期,但是如果希望某些对象具备一定的生命周期的话(比如内存不足时JVM就会自动回收某些对象从而避免OutOfMemory的错误)就需要用到软引用和弱引用了。

  从Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。下面来阐述一下这四种类型引用的概念:

  1.强引用(StrongReference)

  强引用就是指在程序代码之中普遍存在的,比如下面这段代码中的object和str都是强引用:



​Object object = ​​ ​​new​​ ​​Object();​


​String str = ​​ ​​"hello"​​ ​​;​



  只要某个对象有强引用与之关联,JVM必定不会回收这个对象,即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。比如下面这段代码:



​public​​ ​​class​​ ​​Main {​


​public​​ ​​static​​ ​​void​​ ​​main(String[] args) {​


​new​​ ​​Main().fun1();​


​}​


 


​public​​ ​​void​​ ​​fun1() {​


​Object object = ​​ ​​new​​ ​​Object();​


​Object[] objArr = ​​ ​​new​​ ​​Object[​​ ​​1000​​ ​​];​


​}​


​}​



  当运行至Object[] objArr = new Object[1000];这句时,如果内存不足,JVM会抛出OOM错误也不会回收object指向的对象。不过要注意的是,当fun1运行完之后,object和objArr都已经不存在了,所以它们指向的对象都会被JVM回收。

  如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。

  比如Vector类的clear方法中就是通过将引用赋值为null来实现清理工作的:



/**
* Removes the element at the specified position in this Vector.
* Shifts any subsequent elements to the left (subtracts one from their
* indices). Returns the element that was removed from the Vector.
*
* @throws ArrayIndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
* @param index the index of the element to be removed
* @return element that was removed
* @since 1.2
*/
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
Object oldValue = elementData[index];

int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work

return (E)oldValue;
}


  2.软引用(SoftReference)

  软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。

  软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。下面是一个使用示例:



​import​​ ​​java.lang.ref.SoftReference;​


 


​public​​ ​​class​​ ​​Main {​


​public​​ ​​static​​ ​​void​​ ​​main(String[] args) {​


 


​SoftReference<String> sr = ​​ ​​new​​ ​​SoftReference<String>(​​ ​​new​​ ​​String(​​ ​​"hello"​​ ​​));​


​System.out.println(sr.get());​


​}​


​}​



  3.弱引用(WeakReference)

  弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。下面是使用示例:



​import​​ ​​java.lang.ref.WeakReference;​


 


​public​​ ​​class​​ ​​Main {​


​public​​ ​​static​​ ​​void​​ ​​main(String[] args) {​


 


​WeakReference<String> sr = ​​ ​​new​​ ​​WeakReference<String>(​​ ​​new​​ ​​String(​​ ​​"hello"​​ ​​));​


 


​System.out.println(sr.get());​


​System.gc();                ​​ ​​//通知JVM的gc进行垃圾回收​


​System.out.println(sr.get());​


​}​


​}​



  输出结果为:



View Code


  第二个输出结果是null,这说明只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象(软引用也是如此)。

  弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。

  4.虚引用(PhantomReference)

  虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

  要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。



​import​​ ​​java.lang.ref.PhantomReference;​


​import​​ ​​java.lang.ref.ReferenceQueue;​


 


 


​public​​ ​​class​​ ​​Main {​


​public​​ ​​static​​ ​​void​​ ​​main(String[] args) {​


​ReferenceQueue<String> queue = ​​ ​​new​​ ​​ReferenceQueue<String>();​


​PhantomReference<String> pr = ​​ ​​new​​ ​​PhantomReference<String>(​​ ​​new​​ ​​String(​​ ​​"hello"​​ ​​), queue);​


​System.out.println(pr.get());​


​}​


​}​



二.进一步理解软引用和弱引用

  对于强引用,我们平时在编写代码时经常会用到。而对于其他三种类型的引用,使用得最多的就是软引用和弱引用,这2种既有相似之处又有区别。它们都是用来描述非必需对象的,但是被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。

  在SoftReference类中,有三个方法,两个构造方法和一个get方法(WekReference类似):

  两个构造方法:



​public​​ ​​SoftReference(T referent) {​


​super​​ ​​(referent);​


​this​​ ​​.timestamp = clock;​


​}​


 


​public​​ ​​SoftReference(T referent, ReferenceQueue<? ​​ ​​super​​ ​​T> q) {​


​super​​ ​​(referent, q);​


​this​​ ​​.timestamp = clock;​


​}​



  get方法用来获取与软引用关联的对象的引用,如果该对象被回收了,则返回null。

   在使用软引用和弱引用的时候,我们可以显示地通过System.gc()来通知JVM进行垃圾回收,但是要注意的是,虽然发出了通知,JVM不一定会立刻执行,也就是说这句是无法确保此时JVM一定会进行垃圾回收的。

三.如何利用软引用和弱引用解决OOM问题

  前面讲了关于软引用和弱引用相关的基础知识,那么到底如何利用它们来优化程序性能,从而避免OOM的问题呢?

  下面举个例子,假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。

  设计思路是:用一个HashMap来保存图片的路径 和 相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。在Android开发中对于大量图片下载会经常用到。

  下面这段代码是摘自博客:




​.....​


​private​​ ​​Map<String, SoftReference<Bitmap>> imageCache = ​​ ​​new​​ ​​HashMap<String, SoftReference<Bitmap>>();​


​<br>....​


​public​​ ​​void​​ ​​addBitmapToCache(String path) {​


 


​// 强引用的Bitmap对象​


 


​Bitmap bitmap = BitmapFactory.decodeFile(path);​


 


​// 软引用的Bitmap对象​


 


​SoftReference<Bitmap> softBitmap = ​​ ​​new​​ ​​SoftReference<Bitmap>(bitmap);​


 


​// 添加该对象到Map中使其缓存​


 


​imageCache.put(path, softBitmap);​


 


​}​


 


​public​​ ​​Bitmap getBitmapByPath(String path) {​


 


​// 从缓存中取软引用的Bitmap对象​


 


​SoftReference<Bitmap> softBitmap = imageCache.get(path);​


 


​// 判断是否存在软引用​


 


​if​​ ​​(softBitmap == ​​ ​​null​​ ​​) {​


 


​return​​ ​​null​​ ​​;​


 


​}​


 


​// 取出Bitmap对象,如果由于内存不足Bitmap被回收,将取得空​


 


​Bitmap bitmap = softBitmap.get();​


 


​return​​ ​​bitmap;​


 


​}​



  当然这里我们把缓存替换策略交给了JVM去执行,这是一种比较简单的处理方法。复杂一点的缓存,我们可以自己单独设计一个类,这里面就涉及到缓存策略的问题了,具体可以参考之前的一篇博文:《​​缓存算法(页面置换算法)-FIFO、LFU、LRU​​》

  参考资料:

  《深入理解JVM虚拟机》


  ​​http://mobile.51cto.com/abased-406998.htm​

标签:Java,OOM,回收,对象,引用,JVM,new,public
From: https://blog.51cto.com/u_11444530/5938843

相关文章

  • java虚拟机指南
    JVM内存区域我们在编写程序时,经常会遇到OOM(outofMemory)以及内存泄漏等问题。为了避免出现这些问题,我们首先必须对JVM的内存划分有个具体的认识。JVM将内存主要划分为:方法......
  • Java技术点
    1.面经1.1.Redis1.缓存穿透,缓存击穿,缓存雪崩1.缓存穿透缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会被打倒数据库上。......
  • java内存模型
    一.内存模型的相关概念。大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在......
  • Java Object
    jdk提供的一个类,是所有类的父类,再java.lang中Object类中定义了很多方法hashcode():返回对象的散列码toString():以字符串的形式返回某个类的实例化对象的信息getClass()......
  • Java抽象类抽象方法
    有些类定义的方法,他们的每一个子类都要去重写这个方法,那么就可以将这个方法定义成抽象方法,因为无论如何都要重新实现,就没有必要再父类的方法中去做实现了。如果一个类中......
  • JAVA中生成随机数Random VS ThreadLocalRandom性能比较
    前言大家项目中如果有生成随机数的需求,我想大多都会选择使用Random来实现,它内部使用了CAS来实现。实际上,JDK1.7之后,提供了另外一个生成随机数的类ThreadLocalRandom,那么......
  • javaweb7
    JQuery1.介绍1.1web前端知识点回顾HTML决定了网页的内容CSS决定了网页样式JavaScript决定了网页的功能标签属性,样式的操作DOM节点的操作异步交互的操作但是......
  • Java后端获取前端传过来header中的值
    Java后端获取前端传过来header中的值比如获取前端传值token值:方法一:不需要在参数中写HttpServletRequestrequestServletRequestAttributesattributes=(ServletRequ......
  • 肖sir___java自动化__selenium IDE下载和使用
    1.seleniumIDE简介seleniumIDE结合浏览器提供脚本的录制、回放以、编辑脚本功能、以及元素的定位,可以使用seleniumIDE将录制的脚本生成相应的带单元测试框架的自动化测......
  • 彻底搞懂JavaScript防抖与节流
    今天为大家带来一篇JS重难点的知识体系,这也是前端高薪必备的重难点知识,而且防抖与节流在各大企业前端面试过程中经常会考到的高频面试题! 为了更好的帮助大家理解防抖......