什么是内存溢出,那些区域会发生内存溢出
首先我们要知道java是如何运行一个JVM进程,如下代码
代码语言:javascript复制public class Demo3 {
public static void main(String[] args) {
String message = "hello world";
System.out.println(message);
}
}
- 首先是写好的代码编译成class文件
- 执行java命令
- jVM加载你写的类
- main线程执行方法
- 局部变量进入虚拟机栈
- 堆内存创建对象
如上几步,能发生OOM地方
- metaspace区域用来存放类的信息,如果加载的类信息太多就可能导致OOM
- 每个线程都有个虚拟机栈,就是所谓的栈内存,这里存放的是方法的里面的局部变量,栈内存大小一般是1m,不断的循环调用方法,就可能导致OOM
- 堆内存,放一些对象,不断的创建对象,也是可能发生OOM
Metaspace如何触发内存溢出呢
我们用下面参数设置Metaspace内存大小
代码语言:javascript复制-XX:MetaspaceSize=512m
-XX:MaxMetaspaceSize=512m
固定512M,当Metaspace满了之后,就会触发FULL GC,回收的条件也比较苛刻,如这个类加载器被回收,这个类的所有对象实例都被回收等等,所以一旦Metaspace满了,未必会回收里面的很多类,一旦回收之后,还是有很多存活的类,如果继续想Metaspace加入更多的类信息,就会导致OOM
导致Metaspace的一般原因如下
- Metaspace的值太小,只有几十MB,(一般系统512MB)当对于一个稍微大型的系统,由于他有许多的类信心,就可能导致Metaspace不够用
- 有很多系统用cgLIB之类的技术动态生成一些类,一旦代码没有控制住,就会创建过多的类,容易把Metaspace给塞满,进而引发内存溢出
虚拟机栈溢出
我们知道一个线程的虚拟机栈的内存大小是固定的,一般默认是1MB,正如我们之前讲的main()方法,就会产生一个main的栈帧,如下代码
代码语言:javascript复制public class Demo3 {
public static void main(String[] args) {
String message = "hello world";
System.out.println(message);
sayHello("我是肉丝");
}
public static void sayHello(String name){
System.out.println("你好,杰克" name);
}
}
每一个方法的调用,都会在虚拟机栈中创建一个栈帧,保存对应的局部变量,但是此时我们要注意的是每一个帧栈也是要占内存的,虽然一些变量和其他的对象数据占不了太大的内存,但是实际上也是要占用的,
如果一个线程不断的调用各种方法,不停的把方法的栈帧压入虚拟机栈,就会不断的占用这个线程1MB的栈内存,最终会导致栈内存溢出
什么情况下会发生栈内存溢出呢
代码语言:javascript复制public static void sayHello(String name){
sayHello(name);
}
如上代码,出现这种不断递归调用,就有可能导致栈内存溢出,一般来说不会导致栈内存溢出,除非你的bug才会导致
堆内存溢出
- 首先平时我们系统不断创建对象,然后大量的对象进入Eden区,一旦Eden区满之后,就会发生一次YGC,然后存活对象进入S区
- 一旦高并发场景,ygc后很多请求还没有处理完毕,存活对象很多,S区,放不下,就会进入老年代
- 一旦老年代满了,就会发生FULL gc
- 不幸的是老年代GC之后,还是有很多对象存活,此时年轻代不断不断GC,把存活的对象转移到老年代,但是老年代也空间不足了
最终就会由于堆内存实在放不下对象,导致内存溢出,JVM崩溃
一般什么场景会导致堆内存溢出呢
- 系统承载高并发,因为请求量过大,导致大量对象存活,所以要继续放入新的对象实在不行了,就会引起OOM
- 系统内存泄露,就莫名其妙弄很多对象,结果对象都是存活的,没有及时取消他的引用,就会导致GC无法回收,引发内存泄漏最终OOM