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