1. 什么是OOM?
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,因为这个问题已经严重到不足以被应用处理)。
2. 为什么会OOM?
为什么会没有内存了呢?原因不外乎有两点:
1)分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
2)应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
内存泄露:申请的内存在被使用完后没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请该内存的对象不再使用该内存,而该段内存又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
在之前没有垃圾自动回收的日子里,比如C语言和C++语言,我们必须亲自负责内存的申请与释放操作,如果申请了内存,用完后又忘记了释放,比如C++中的new了但是没有delete,那么就可能造成内存泄露。偶尔的内存泄露可能不会造成问题,而大量的内存泄露可能会导致内存溢出。
而在Java语言中,由于存在了垃圾自动回收机制,所以,我们一般不用去主动释放不用的对象所占的内存,也就是理论上来说,是不会存在“内存泄露”的。但是,如果编码不当,比如,将某个对象的引用放到了全局的Map中,虽然方法结束了,但是由于垃圾回收器会根据对象的引用情况来回收内存,导致该对象不能被及时的回收。如果该种情况出现次数多了,就会导致内存溢出,比如系统中经常使用的缓存机制。Java中的内存泄露,不同于C++中的忘了delete,往往是逻辑上的原因泄露。
=================================================================
2.1 内存溢出和内存泄露的区别:
1、内存溢出:程序在分配内存的时候没有足够大的空间了。
2、内存泄漏:程序在申请内存之后,没有办法释放掉内存,它始终占用着内存,即被分配的对象可达但无用。内存泄露一般都是因为内存中有一块很大的对象,但是无法释放。 会导致内存溢出。
3. OOM的类型
3.1 按照JVM规范,JAVA虚拟机在运行时会管理以下的内存区域:
-
程序计数器:当前线程执行的字节码的行号指示器,线程私有
-
JAVA虚拟机栈:Java方法执行的内存模型,每个Java方法的执行对应着一个栈帧的进栈和出栈的操作。
-
本地方法栈:类似“ JAVA虚拟机栈 ”,但是为native方法的运行提供内存环境。
-
JAVA堆:对象内存分配的地方,内存垃圾回收的主要区域,所有线程共享。可分为新生代,老生代。
-
方法区:用于存储已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Hotspot中的“永久代”。
-
运行时常量池:方法区的一部分,存储常量信息,如各种字面量、符号引用等。
-
直接内存:并不是JVM运行时数据区的一部分, 可直接访问的内存, 比如:NIO会用到这部分。
按照JVM规范,除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。
3.2 最常见的OOM情况有以下三种 + 解决办法
-
java.lang.OutOfMemoryError: Java heap space —— > java 堆内存溢出
-
出现OOM原因:此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。
-
解决办法:对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms、-Xmx等修改。
-
举例:循环调用new A() 导致堆溢出
虚拟机参数:-Xms1M -Xmx1M -XX:+HeapDumpOnOutOfMemoryError,
解释:将-Xmx和-Xms设置为一样可以避免堆自动扩展。
-XX:+HeapDumpOnOutOfMemoryError, 解释:可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照,然后分析Dump文件
-
while (true){
new A().do2();
}
-
java.lang.OutOfMemoryError: PermGen space —— > java 永久代溢出 ,即方法区溢出了
-
出现OOM原因:一般出现于大量Class 或者 jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。另外,过多的常量,尤其是字符串,也会导致方法区溢出。
-
解决办法:此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m 的形式修改。
-
举例:循环调用String.intern()方法来写入常量池,常量池溢出。
虚拟机参数:-XX:PermSize=10M -XX:MaxPermSize=10M,
解释:表示JVM初始分配的永久代的容量和最大容量。(永久区内存不足,1.8后都在堆上。方法区=永久代,PermGen space”,即永久代)
-
while (true){
list.add(String.valueOf(i++).intern());
}
-
java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。
-
出现OOM原因:JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。
-
解决办法 :可以通过虚拟机参数 -Xss 来设置栈的大小。
-
举例:循环调用对象引用的方式实现栈溢出。
虚拟机参数:-Xss128k,
解释:设置虚拟机栈的大小为128kn
在单线程下,无论栈帧太大还是虚拟机栈容量太小,内存无法分配的时候都会抛出以上错误。
-
void do2(){
do2();
};