什么是内存溢出?在哪些区域会发生内存溢出?

什么是内存溢出?在哪些区域会发生内存溢出?回答这个问题,我们需要先看一看Java代码是怎么运行的。现在计入说我们写了一个 HelloWorld.class

1
2
3
4
5
6
public class HelloWorld {
    public static void main(String[] args){
        String message = "Hello World";
        System.out.println(message);
    }
}

我们来分析一下这段代码的运行情况:

  1. 源文件 HelloWorld.java 将会被编译成可执行文件 HelloWorld.class
  2. 类加载加载可执行文件到 Metaspace,Metaspace 保存类的基本信息,如果加载太多就会 OOM
  3. Java是多线程的,运行代码的时候会启动一个线程。main()是Java程序的入口,首先会启动一个 main 线程。每个线程都有 Java 虚拟机栈,每执行一个方法都会有一个栈帧入栈,栈帧中包含参数、局部变量、返回值地址等信息。如果代码层次太深,不断有方法入栈却没有出栈,Java虚拟机栈就会 OOM。
  4. 栈中的局部变量如果是一个对象,那就会在初始化的时候在堆中创建对象。堆中创建的对象过多就会触发 GC,GC 的速度赶不上新建对象的速度也会发生 OOM。

从 Java 代码的运行过程来看,有三个区域会发生 OOM,它们分别是:Metaspace、Java 虚拟机栈、堆内存。

Metaspace 是如何溢出的?

Metaspace 中会加载类,保存各种类信息,它的大小可以通过 -XX:MetaspaceSize=512m-XX:MaxMetaspaceSize=512m 来设置。

当我们不断地创建类,不断地创建类,把 Metaspace 的内存都给占满了。这个时候就会触发 Full GC,Full GC 一般会顺带着进行 Minor GC 回收年轻带,也会进行 Old GC 回收老年代。

如果 Full GC 回收 Metespace 中的空间之后,任然无法存放新建的对象,此时 OOM 就会发生了。

不过 Metaspace 一般很少发生 OOM,如果发生了一般是出于下面两个原因:

  1. 上线系统的时候对 Metaspace 区域直接用默认的参数,即根本不设置其大小。默认的Metaspace区域可能才几十MB,对于稍微大型的系统,他自己有很多类,还依赖了很多外部的jar包,几十MB的Metaspace很容易就不够了。
  2. 写代码的时候用到了类似与 cglib 的动态代理技术,代码写得不好就会导致创建太多的类。

线程中的栈是如何溢出的?

虚拟机中的栈内存也是有限的,我们调用方法的时候会创建一个栈帧,紧接着方法入栈。如果一个线程一直调用方法入栈,栈内存终归是要满的,此时线程的栈中就会发生 OOM。

发生这种情况一般就是代码除了问题,比如写了个递归调用,和 Metaspace 的内存溢出一样,也很少发生。

堆内存是如何溢出的?

最常见的就是堆内存溢出的问题了。堆内存有限的情况下,不断创建对象并触发 GC,但是 GC 过后任然有大量大象存活,此时还要放入大量新的对象,这就会导致堆内存溢出问题。

一般来说对内存溢出有这样两个场景:

  1. 高并发场景下,请求量太大,创建了大量新的对象,且这些都是有用的、存活的。堆中无法放入更多对象就会导致堆内存溢出
  2. 内存泄漏问题,长生命周期的对象引用了大量短生命周期的对象,没有及时取消对它们的引用,导致 GC 无法回收这些理应被回收的对象,就导致了堆内存溢出
发布了190 篇原创文章 · 获赞 17 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/shuiCSDN/article/details/104001065