内存溢出的原因与解决办法(3种内存溢出详解)

内存溢出的原因与解决办法(3种内存溢出详解)-mikechen

内存溢出(out of memory)是程序经常出现的问题,是什么导致内存溢出?有哪些原因及解决办法?接下来我来详解@mikechen

什么是内存溢出

内存溢出,简称OOM,内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

内存溢出的原因与解决办法(3种内存溢出详解)-mikechen

比如申请了一个integer, 但给它存了long才能存下的数,那就是内存溢出。

再比如:你的电脑只有16GB的内存,已经把这16GB的内存用完了,但是还在继续用,就会造成内存溢出。

 

内存溢出的原因

1.程序常见的内存溢出原因

  • 启动参数内存值设定的过小
  • 内存中加载的数据量过于庞大;
  • 对象的引用使用完后未清空,使得 JVM 不能回收;
  • 代码中存在死循环或循环产生过多重复的对象实体;

 

2.Web服务器常见内存溢出提示

在不同的Web服务器或程序中,此错误常见的错误提示如下:

  • tomcat: java.lang.OutOfMemoryError: PermGen space
  • tomcat: java.lang.OutOfMemoryError: Java heap space
  • weblogic: Root cause of ServletException java.lang.OutOfMemoryError
  • resin: java.lang.OutOfMemoryError
  • java: java.lang.OutOfMemoryError

这些都是属于典型的内存溢出,接下来我们看看具体的内存溢出的解决办法。

 

内存溢出的解决办法

这里我就以Java举例,常见的Java内存溢出主要是以下三种方式:

内存溢出的原因与解决办法(3种内存溢出详解)-mikechen

1.堆溢出

这是很常见的一种内存溢出,报错信息如下:

java.lang.OutOfMemoryError: Java heap space

举一个死循环的例子,源码如下:

package com.mikechen.jvm;

/**
 * JVM 内存溢出案例详解
 *
 * @author  mikechen
 *
 */

import java.util.ArrayList;
import java.util.List;

public class MemoryOOM {

    static class Obj{ }

    /** * -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * */

    public static void main(String[] args) {
        List<Obj> list = new ArrayList<>();
        try {
            while (true){
                list.add(new Obj());
            }
        }catch (Throwable t){
            t.printStackTrace();
            System.out.println("集合大小"+list.size());
        }
    }
}

这里会不断死循环list.add(new Obj()),不断的创建对象,当不总容量触及最大堆容量时,就会产生溢出,出现如下提示:

内存溢出的原因与解决办法(3种内存溢出详解)-mikechen

解决办法:

1、首先排查,检查是否存在大对象的分配,最有可能的是大对象分配。
2、其次,再通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题。
3、如果没有找到明显的内存泄露,可以适当调整-Xms和-Xmx两个JVM参数。

比如上面的例子,在第二个步骤通过dump下来的堆,用工具可以分析出死循环的位置,就可以及时解决堆溢出。

如果通过上面的两个步骤都排查后,没有发现问题,再调整JVM堆的初始化参数,问题基本就可以解决。

 

2.堆内存溢出

首先搞清楚java栈空间存储的是什么,栈内存可以分为虚拟机栈(VM Stack)和本地方法栈(Native Method Stack)。

每个方法执行时都会在java栈空间产生一个栈帧,存放方法的变量表,返回值等信息,方法的执行到结束就是一个栈帧入栈到出栈的过程。

具体来说,当线程执行某个方法时,JVM会创建栈帧并压栈,此时刚压栈的栈帧就成为了当前栈帧。

如果该方法进行递归调用时,JVM每次都会将保存了当前方法数据的栈帧压栈,每次栈帧中的数据都是对当前方法数据的一份拷贝,如果递归的次数足够多,多到栈中栈帧所使用的内存超出了栈内存的最大容量,此时JVM就会抛出StackOverflowError。

还是看一个例子:

public class stack{
 
    public void test(){
 
        this.test();
    }
    public static void main(String[] args){
        for(; ; ;)
            new stack().test;
    }
}

最后就会出现:

StackOverflowError的错误提示

总之,不论是因为栈帧太大还是栈内存太小,当新的栈帧内存无法被分配时,JVM就会抛出StackOverFlowError,通常栈内存可以通过设置-Xss参数来改变大小。

 

3.永久代/元空间溢出

永久区(Perm) 存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。

在JDK 1.8中,永久区被一块称为元数据的区域替代, 但是它们的功能是类似的,都是为了保存类的元信息,如果一 一个系统定了太多的类型,那么永久区是有可能溢出的,报错信息如下:

java.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: Metaspace

解决方法有如下几种:

1、检查是否永久代空间或者元空间设置的过小

2、检查代码中是否存在大量的反射操作

3、dump之后通过mat检查是否存在大量由于反射生成的代理类

4、放大招,重启JVM

 

作者简介

陈睿|mikechen,10年+大厂架构经验,BAT资深面试官,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

👇阅读更多mikechen架构文章👇

阿里架构 |双11秒杀 |分布式架构 |负载均衡 |单点登录 |微服务 |云原生 |高并发 |架构师

以上

关注作者「mikechen」公众号,获取更多技术干货!

后台回复架构,即可获取《阿里架构师进阶专题全部合集》,后台回复面试即可获取《史上最全阿里Java面试题总结

评论交流
    说说你的看法