本篇文章将深入浅出的介绍Java中的内存溢出与内存透露并阐明强援用、软援用、弱援用、虚援用的特点与应用场景
援用
在栈上的reference
类型存储的数据代表某块内存地址,称reference
为某内存、某对象的援用
实际上援用分为很多种,从强到弱分为:强援用 > 软援用 > 弱援用 > 虚援用
平时咱们应用的援用实际上是强援用,各种援用有本人的特点,下文将一一介绍
强援用就是Java中一般的对象,而软援用、弱援用、虚援用在JDK中定义的类别离是SoftReference
、WeakReference
、PhantomReference
下图是软援用、弱援用、虚援用、援用队列(搭配虚援用应用)之间的继承关系
内存溢出与内存透露
为了更革除的形容援用之间的作用,首先须要介绍一下内存溢出和内存透露
当产生内存溢出时,示意JVM没有闲暇内存为新对象调配空间,抛出OutOfMemoryError(OOM)
当应用程序占用内存速度大于垃圾回收内存速度时就可能产生OOM
抛出OOM之前通常会进行Full GC,如果进行Full GC后仍旧内存不足才抛出OOM
JVM参数-Xms10m -Xmx10m -XX:+PrintGCDetails
内存溢出可能产生的两种状况:
- 必须的资源的确很大,堆内存设置太小 (通过
-Xmx
来调整)
<!---->
- 产生内存透露,创立大量对象,且生命周期长,不能被回收
内存透露Memory Leak: 对象不会被程序用到了,然而不能回收它们
对象不再应用并且不能回收就会始终占用空间,大量对象产生内存透露可能产生内存溢出OOM
狭义内存透露:不正确的操作导致对象生命周期变长
- 单例中援用内部对象,当这个内部对象不必了,然而因为单例还援用着它导致内存透露
- 一些须要敞开的资源未敞开导致内存透露
强援用
强援用是程序代码中普遍存在的援用赋值,比方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 公布!