共计 4347 个字符,预计需要花费 11 分钟才能阅读完成。
前言
OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于 java.lang.OutOfMemoryError。
看下对于的官网阐明 :Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. 意思就是说,当 JVM 因为没有足够的内存来为对象调配空间并且垃圾回收器也曾经没有空间可回收时,就会抛出这个 error
(注:非 exception,因为这个问题曾经重大到不足以被利用解决)。
动态汇合类
如 HashMap、LinkedList 等等。如果这些容器为动态的,那么它们的生命周期与程序统一,则容器中的对象在程序完结之前将不能被开释,从而造成内存透露。简略而言,长生命周期的对象持有短生命周期对象的援用,只管短生命周期的对象不再应用,然而因为长生命周期对象持有它的援用而导致不能被回收。
各种连贯,如数据库连贯、网络连接和 IO 连贯等
在对数据库进行操作的过程中,首先须要建设与数据库的连贯,当不再应用时,须要调用 close 办法来开释与数据库的连贯。只有连贯被敞开后,垃圾回收器才会回收对应的对象。否则,如果在拜访数据库的过程中,对 Connection、Statement 或 ResultSet 不显性地敞开,将会造成大量的对象无奈被回收,从而引起内存透露。
变量不合理的作用域
一般而言,一个变量的定义的作用范畴大于其应用范畴,很有可能会造成内存透露。另一方面,如果没有及时地把对象设置为 null,很有可能导致内存透露的产生。
public class UsingRandom {private String msg;public void receiveMsg() {readFromNet(); // 从网络中承受数据保留到 msg 中 saveDB(); // 把 msg 保留到数据库中}}
复制代码
如下面这个伪代码,通过 readFromNet 办法把承受的音讯保留在变量 msg 中,而后调用 saveDB 办法把 msg 的内容保留到数据库中,此时 msg 曾经就没用了,因为 msg 的生命周期与对象的生命周期雷同,此时 msg 还不能回收,因而造成了内存透露。
实际上这个 msg 变量能够放在 receiveMsg 办法外部,当办法应用完,那么 msg 的生命周期也就完结,此时就能够回收了。还有一种办法,在应用完 msg 后,把 msg 设置为 null,这样垃圾回收器也会回收 msg 的内存空间。
外部类持有外部类
如果一个外部类的实例对象的办法返回了一个外部类的实例对象,这个外部类对象被长期援用了,即便那个外部类实例对象不再被应用,但因为外部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
扭转哈希值
当一个对象被存储进 HashSet 汇合中当前,就不能批改这个对象中的那些参加计算哈希值的字段了,否则,对象批改后的哈希值与最后存储进 HashSet 汇合中时的哈希值就不同了,在这种状况下,即便在 contains 办法应用该对象的以后援用作为的参数去 HashSet 汇合中检索对象,也将返回找不到对象的后果,这也会导致无奈从 HashSet 汇合中独自删除以后对象,造成内存泄露
举个例子 - 看你是否找出内存透露
import java.util.Arrays;public class Stack {private Object[] elements;private int size = 0;private static final int DEFAULT_INITIAL_CAPACITY = 16;public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; }public void push(Object e) {ensureCapacity(); elements[size++] = e; }public Object pop() {if (size == 0)throw new EmptyStackException();return elements[--size]; }private void ensureCapacity() {if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); }} 起因剖析
复制代码
上述程序并没有显著的谬误,然而这段程序有一个内存透露,随着 GC 流动的减少,或者内存占用的一直减少,程序性能的升高就会体现进去,重大时可导致内存透露,然而这种失败状况绝对较少。
代码的次要问题在 pop 函数,上面通过这张图示展示
假如这个栈始终增长,增长后如下图所示
当进行大量的 pop 操作时,因为援用未进行置空,gc 是不会开释的,如下图所示
从上图中看以看出,如果栈先增长,在膨胀,那么从栈中弹出的对象将不会被当作垃圾回收,即便程序不再应用栈中的这些对象,他们也不会回收,因为栈中依然保留这对象的援用,俗称过期援用,这个内存泄露很荫蔽。
解决办法
public Object pop() {if (size == 0)throw new EmptyStackException();Object result = elements[--size]; elements[size] = null;return result;}
复制代码
一旦援用过期,清空这些援用,将援用置空。
缓存透露
内存透露的另一个常见起源是缓存,一旦你把对象援用放入到缓存中,他就很容易忘记,对于这个问题,能够应用 WeakHashMap 代表缓存,此种 Map 的特点是,当除了本身有对 key 的援用外,此 key 没有其余援用那么此 map 会主动抛弃此值
1. 代码示例
package com.ratel.test;/** * @业务形容:* @package_name:com.ratel.test * @project_name:ssm * @author:ratelfu@qq.com * @copyright (c) ratelfu 版权所有 */import java.util.HashMap;import java.util.Map;import java.util.WeakHashMap;import java.util.concurrent.TimeUnit;public class MapTest {static Map wMap = new WeakHashMap();static Map map = new HashMap();public static void main(String[] args) {init(); testWeakHashMap(); testHashMap(); }public static void init(){String ref1= new String("obejct1");String ref2 = new String("obejct2");String ref3 = new String ("obejct3");String ref4 = new String ("obejct4"); wMap.put(ref1, "chaheObject1"); wMap.put(ref2, "chaheObject2"); map.put(ref3, "chaheObject3"); map.put(ref4, "chaheObject4"); System.out.println("String 援用 ref1,ref2,ref3,ref4 隐没"); }public static void testWeakHashMap(){ System.out.println("WeakHashMap GC 之前");for (Object o : wMap.entrySet()) {System.out.println(o); }try {System.gc(); TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) {// TODO Auto-generated catch block e.printStackTrace(); } System.out.println("WeakHashMap GC 之后");for (Object o : wMap.entrySet()) {System.out.println(o); } }public static void testHashMap(){ System.out.println("HashMap GC 之前");for (Object o : map.entrySet()) {System.out.println(o); }try {System.gc(); TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) {// TODO Auto-generated catch block e.printStackTrace(); } System.out.println("HashMap GC 之后");for (Object o : map.entrySet()) {System.out.println(o); } }}/** 后果 String 援用 ref1,ref2,ref3,ref4 隐没 WeakHashMap GC 之前 obejct2=chaheObject2 obejct1=chaheObject1 WeakHashMap GC 之后 HashMap GC 之前 obejct4=chaheObject4 obejct3=chaheObject3 Disconnected from the target VM, address: '127.0.0.1:51628', transport: 'socket' HashMap GC 之后 obejct4=chaheObject4 obejct3=chaheObject3 **/
复制代码
下面代码和图示主演演示 WeakHashMap 如何主动开释缓存对象,当 init 函数执行实现后,局部变量字符串援用 weakd1,weakd2,d1,d2 都会隐没,此时只有动态 map 中保留中对字符串对象的援用,能够看到,调用 gc 之后,hashmap 的没有被回收,而 WeakHashmap 外面的缓存被回收了。
监听器和回调
内存透露第三个常见起源是监听器和其余回调,如果客户端在你实现的 API 中注册回调,却没有显示的勾销,那么就会积累。须要确保回调立刻被当作垃圾回收的最佳办法是只保留他的若援用,例如将他们保留成为 WeakHashMap 中的键。
参考:《2020 最新 Java 根底精讲视频教程和学习路线!》
链接:https://juejin.cn/post/693866…