关于后端:深入浅出JVM十四之内存溢出泄漏与引用

1次阅读

共计 5794 个字符,预计需要花费 15 分钟才能阅读完成。

本篇文章将深入浅出的介绍 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 公布!

正文完
 0