前言

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...