本篇文章将深入浅出的介绍Java中的内存溢出与内存透露并阐明强援用、软援用、弱援用、虚援用的特点与应用场景

援用

在栈上的reference类型存储的数据代表某块内存地址,称reference为某内存、某对象的援用

实际上援用分为很多种,从强到弱分为:强援用 > 软援用 > 弱援用 > 虚援用

平时咱们应用的援用实际上是强援用,各种援用有本人的特点,下文将一一介绍

强援用就是Java中一般的对象,而软援用、弱援用、虚援用在JDK中定义的类别离是SoftReferenceWeakReferencePhantomReference

下图是软援用、弱援用、虚援用、援用队列(搭配虚援用应用)之间的继承关系

内存溢出与内存透露

为了更革除的形容援用之间的作用,首先须要介绍一下内存溢出和内存透露

当产生内存溢出时,示意JVM没有闲暇内存为新对象调配空间,抛出OutOfMemoryError(OOM)

当应用程序占用内存速度大于垃圾回收内存速度时就可能产生OOM

抛出OOM之前通常会进行Full GC,如果进行Full GC后仍旧内存不足才抛出OOM

JVM参数-Xms10m -Xmx10m -XX:+PrintGCDetails

内存溢出可能产生的两种状况:

  1. 必须的资源的确很大,堆内存设置太小 (通过-Xmx来调整)

<!---->

  1. 产生内存透露,创立大量对象,且生命周期长,不能被回收

内存透露Memory Leak: 对象不会被程序用到了,然而不能回收它们

对象不再应用并且不能回收就会始终占用空间,大量对象产生内存透露可能产生内存溢出OOM

狭义内存透露:不正确的操作导致对象生命周期变长

  1. 单例中援用内部对象,当这个内部对象不必了,然而因为单例还援用着它导致内存透露
  2. 一些须要敞开的资源未敞开导致内存透露

强援用

强援用是程序代码中普遍存在的援用赋值,比方List list = new ArrayList();

只有强援用在可达性剖析算法中可达时,垃圾收集器就不会回收该对象,因而不当的应用强援用是造成Java内存透露的次要起因

软援用

当内存短缺时不会回收软援用

只有当内存不足时,产生Full GC时才将软援用进行回收,如果回收后还没短缺内存则抛出OOM异样

JVM中针对不同的区域(年老代、老年代、元空间)有不同的GC形式,Full GC的回收区域为整个堆和元空间

软援用应用SoftReference

内存短缺状况下的软援用
     public static void main(String[] args) {         int[] list = new int[10];         SoftReference listSoftReference = new SoftReference(list);         //[I@61bbe9ba         System.out.println(listSoftReference.get());     }
内存不短缺状况下的软援用(JVM参数:-Xms5m -Xmx5m -XX:+PrintGCDetails)
 //-Xms5m -Xmx5m -XX:+PrintGCDetails public class SoftReferenceTest {     public static void main(String[] args) {         int[] list = new int[10];         SoftReference listSoftReference = new SoftReference(list);         list = null;          //[I@61bbe9ba         System.out.println(listSoftReference.get());          //模仿空间资源有余         try{             byte[] bytes = new byte[1024 * 1024 * 4];             System.gc();         }catch (Exception e){             e.printStackTrace();         }finally {             //null             System.out.println(listSoftReference.get());         }     } }

弱援用

无论内存是否足够,当产生GC时都会对弱援用进行回收

弱援用应用WeakReference

内存短缺状况下的弱援用
     public static void test1() {         WeakReference<int[]> weakReference = new WeakReference<>(new int[1]);         //[I@511d50c0         System.out.println(weakReference.get());          System.gc();                  //null         System.out.println(weakReference.get());     }
WeakHashMap

JDK中有一个WeakHashMap,应用与Map雷同,只不过节点为弱援用

当key的援用不存在援用的状况下,产生GC时,WeakHashMap中该键值对就会被删除

     public static void test2() {         WeakHashMap<String, String> weakHashMap = new WeakHashMap<>();         HashMap<String, String> hashMap = new HashMap<>();          String s1 = new String("3.jpg");         String s2 = new String("4.jpg");          hashMap.put(s1, "图片1");         hashMap.put(s2, "图片2");         weakHashMap.put(s1, "图片1");         weakHashMap.put(s2, "图片2");          //只将s1赋值为空时,堆中的3.jpg字符串还会存在强援用,所以要remove         hashMap.remove(s1);         s1=null;         s2=null;          System.gc();          //4.jpg=图片2         test2Iteration(hashMap);          //4.jpg=图片2         test2Iteration(weakHashMap);     }      private static void test2Iteration(Map<String, String>  map){         Iterator iterator = map.entrySet().iterator();         while (iterator.hasNext()){            Map.Entry entry = (Map.Entry) iterator.next();             System.out.println(entry);         }     }

未显示删除weakHashMap中的该key,当这个key没有其余中央援用时就删除该键值对

软援用,弱援用实用的场景

数据量很大占用内存过多可能造成内存溢出的场景

比方须要加载大量数据,全副加载到内存中可能造成内存溢出,就能够应用软援用、弱援用来充当缓存,当内存不足时,JVM对这些数据进行回收

应用软援用时,能够自定义Map进行存储Map<String,SoftReference<XXX>> cache

应用弱援用时,则能够间接应用WeakHashMap

软援用与弱援用的区别则是GC回收的机会不同,软援用存活可能更久,Full GC下才回收;而弱援用存活可能更短,产生GC就会回收

虚援用

应用PhantomReference创立虚援用,须要搭配援用队列ReferenceQueue应用

无奈通过虚援用失去该对象实例(其余援用都能够失去实例)

虚援用只是为了能在这个对象被收集器回收时收到一个告诉

援用队列搭配虚援用应用
 public class PhantomReferenceTest {     private static PhantomReferenceTest reference;     private static ReferenceQueue queue;      @Override     protected void finalize() throws Throwable {         super.finalize();         System.out.println("调用finalize办法");         //搭上援用链         reference = this;     }      public static void main(String[] args) {         reference = new PhantomReferenceTest();         //援用队列         queue = new ReferenceQueue<>();         //虚援用         PhantomReference<PhantomReferenceTest> phantomReference = new PhantomReference<>(reference, queue);                  Thread thread = new Thread(() -> {             PhantomReference<PhantomReferenceTest> r = null;             while (true) {                 if (queue != null) {                     r = (PhantomReference<PhantomReferenceTest>) queue.poll();                     //阐明被回收了,失去告诉                     if (r != null) {                         System.out.println("实例被回收");                     }                 }             }         });         thread.setDaemon(true);         thread.start();           //null (获取不到虚援用)         System.out.println(phantomReference.get());          try {             System.out.println("第一次gc 对象能够复活");             reference = null;             //第一次GC 援用不可达 守护线程执行finalize办法 从新变为可达对象             System.gc();             TimeUnit.SECONDS.sleep(1);             if (reference == null) {                 System.out.println("object is dead");             } else {                 System.out.println("object is alive");             }             reference = null;             System.out.println("第二次gc 对象死了");             //第二次GC 不会执行finalize办法 不能再变为可达对象             System.gc();             TimeUnit.SECONDS.sleep(1);             if (reference == null) {                 System.out.println("object is dead");             } else {                 System.out.println("object is alive");             }         } catch (InterruptedException e) {             e.printStackTrace();         }      } }

后果:

 /* null 第一次gc 对象能够复活 调用finalize办法 object is alive 第二次gc 对象死了 实例被回收 object is dead */

第一次GC时,守护线程执行finalize办法让虚援用从新可达,所以没死

第二次GC时,不再执行finalize办法,虚援用已死

虚援用回收后,援用队列有数据,来告诉通知咱们reference这个对象被回收了

应用场景

GC只能回收堆内内存,而间接内存GC是无奈回收的,间接内存代表的对象创立一个虚援用,退出援用队列,当这个间接内存不应用,这个代表间接内存的对象为空时,这个虚内存就死了,而后援用队列会产生告诉,就能够告诉JVM去回收堆外内存(间接内存)

总结

本篇文章围绕援用深入浅出的解析内存溢出与透露、强援用、软援用、弱援用、虚援用

当JVM没有足够的内存为新对象调配空间时就会产生内存溢出抛出OOM

内存溢出有两种状况,一种是调配的资源太少,不满足必要对象的内存;另一种是产生内存透露,不合理的设置对象的生命周期、不敞开资源都会导致内存透露

应用最常见的就是强援用,强援用只有在可达性剖析算法中不可达时才会回收,强援用使用不当是造成内存透露的起因之一

应用SoftReference软援用时,只有内存不足触发Full GC时就会对软援用进行回收

应用WeakReference弱援用时,只有产生GC就会对弱援用进行回收

软、弱援用能够用来充当大数据状况下的缓存,它们的区别就是软援用可能活的更久Full GC才回收,应用弱援用时能够间接应用JDK中提供的WeakHashMap

虚援用无奈在程序中获取,与援用队列搭配应用,当虚援用被回收时,可能从援用队列中取出(感知),能够在间接援用不应用时,收回音讯让JVM进行回收

最初(一键三连求求拉~)

本篇文章将被支出JVM专栏,感觉不错感兴趣的同学能够珍藏专栏哟~

本篇文章笔记以及案例被支出 gitee-StudyJava、 github-StudyJava 感兴趣的同学能够stat下继续关注喔\~

有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下\~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 公布!