JVM中的主要内存区域包括了以下几种:
- 元空间
这一块区域在JDK1.7之前叫做方法区,在JDK1.8之后改成了元空间(Metaspace),这一块内存区域主要是存放了从“.class”文件里加载进来的类,还会有一些类似常量池的东西放在这个区域里。
- 程序计数器
程序计数器就是用来记录当前执行的字节码指令的位置的,也就是记录目前执行到了哪一条字节码指令。
- Java虚拟机栈
Java虚拟机栈主要是用来保存每个方法内的局部变量,每个线程都有对应的虚拟机栈,比如说main线程执行时,它的虚拟机栈中就保存了main()方法中的局部变量,当执行main()方法时,线程就会给main()方法创建一个栈帧,压入到Java虚拟机栈中,栈帧里就有这个方法的局部变量表 、操作数栈、动态链接、方法出口等,如果main()方法中还执行了其他的方法的话,那么又会往Java虚拟机栈压入其他方法的栈帧,方法执行完毕之后栈帧出栈,当最后main()方法执行完毕,main()方法栈帧出栈。
- 本地方法栈
本地方法栈类型于Java虚拟机栈,但是执行的方法都是Native方法,也就是非Java的方法。
- Java堆
虚拟机中所管理的内存中区域最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。java堆是垃圾收集器管理的主要区域。
哪几块区域会存在OOM内存溢出的问题?
通过这张图来理解一下,当我们的源文件编译为字节码文件,然后通过类加载进入到JVM中,首先我们的类的信息会存放在MetaSpace这一块内存区域中,那么就存在一个问题,MetaSpace元空间的大小并不是无限的,一般也就是几百M,那么如果其中存放了太多的类信息,并且在Full GC之后无法回收掉这些类信息的话,就会导致OOM产生。
一般MetaSpace很少会发生OOM的情况,如果发生,一般是两种情况:
- 未正确设置的MetaSpace参数大小,如果系统中会产生大量的类信息,但是MetaSpace却给了一个很小的值或者直接使用默认值的话,可能就会导致MetaSpace内存溢出。
- 编写代码时大量使用cglib等动态代理方式去生成类,一旦没有控制好,就会产生大量的类导致MetaSpace的空间不足。
当我们开始执行类中的方法,Java虚拟机栈会将方法的栈帧压入虚拟机栈中,一般一个虚拟机栈的大小大概1M就够用了。但是如果代码中出现了bug,递归的调用的方法,那么方法的栈帧就会不断的被压入栈帧中,最终就会导致Java虚拟机栈发生OOM内存溢出。
最后一块可能会产生OOM的区域就是Java堆,也是最容易产生OOM的内存区域,MetaSpace和虚拟机栈一般代码没有bug的情况下是不会产生OOM异常的。
当方法不断的创建对象时,首先对象会先被分配在Eden区中,当Eden区放不下时,就会触发Young GC,然后存活的对象被放入Survivor区,此时还在不断的产生新的对象,若存活的对象经过Young GC无法再放入Survivor区时,就会直接进入老年代,老年代的对象也不断增加,最终产生Full GC。最坏的情况是Full GC之后,老年代还是存活了大量的对象,此时Young GC之后存活的对象进入到老年代中,老年代也放不下了,然后就会产生OOM内存溢出。
堆内存发生OOM的根本原因就是在有限的内存中放了过多的对象,而且大多数都是存活的,此时即使GC过后还是大部分都存活,所以要继续放入更多对象已经不可能了,此时只能引发内存溢出问题。
一般堆内存产生OOM的原因主要有两个
- 系统并发量过大,大量的对象都是存活的,最终堆内存不堪重负产生OOM。
- 系统发生了内存泄漏,莫名其妙产生了大量的对象,且没有及时取消对它们的引用,导致OOM。
堆内存的OOM问题主要是代码bug或是系统设计有缺陷,比较容易产生,需要更加注意。