什么是OOM?为什么会出现OOM?
概念
OOM
,全称“Out Of Memory
”,意思是“内存用完了”来源于
java.lang.OutOfMemoryError
这是个特别严重的问题,因为这个问题已经 严重到应用程序自己无法处理了。
原因
> 官方的文档称,当JVM
因为没有足够的内存
来为对象分配空间
并且垃圾回收器也已经没有空间可回收
时,就会抛出 java.lang.OutOfMemoryError: ···
具体原因一般有这两个:
- 自身原因:
分配的内存少了
,比如JVM虚拟机本身可使用的内存(一般通过启动时的VM参数
指定)太少。-XX:+HeapDumpOnOutOfMemoryError -Xms20m -Xmx20m -XX:HeapDumpPath=D:\oomTemp
- ``-XX:+HeapDumpOnOutOfMemoryError`:表示 导出内存溢出的堆信息(hprof文件)
-Xms
:表示 设定程序启动时占用内存大小-Xmx
:表示 程序运行期间最大可占用的内存大小-XX:HeapDumpPath=
:表示 生成得快照路径
- 外部原因:内存被应用程序使用的太多,而且用完后没有释放,浪费了内存。这种情况下会造成
内存泄漏
or内存溢出
:内存泄漏
:应用进程申请并使用完的内存,没有被释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为当前的申请者不用了,但又不能被JVM虚拟机分配给其他申请者。内存溢出
:申请的内存超出了JVM虚拟机能提供的内存大小。
JVM内存模型
> 这部分内容来自我的上一篇文章:[JVM|内存模型] Java虚拟机的内存模型?也就这7个而已
Java虚拟机所管理的内存包括以下 7个
运行时数据区域:
程序计数器
(Program Counter Register)
> - 一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器
> - 线程私有
的内存
> - 值得注意的是:《Java虚拟机规范》中,唯一
一个没有规定任何OutOfMemoryError情况
的区域!!!
Java虚拟机栈
(VM Stack)
> - Java方法执行的线程内存模型
> - 为虚拟机执行Java方法(也就是字节码
)服务
> - 线程私有
的内存
> - 其生命周期与线程相同
> - 每个Java方法的执行对应着一个栈帧的进栈和出栈的操作
> - 两类异常:
> - 如果线程请求的栈深度大于虚拟机所允许的深度
,将抛出StackOverflowError
异常
> - 如果JVM栈容量可以动态扩展
,当栈扩展时无法申请到足够的内存
时,会抛出OutOfMemoryError
异常
本地方法栈
(Native Method Stacks)
> - 区别于 “Java虚拟机栈” :本地方法栈
只为虚拟机使用到的本地(Native)方法
服务,为其运行提供内存环境
> - 同 “Java虚拟机栈” 一样,本地方法栈
也有两类异常:
> - 栈深度溢出
时,将抛出StackOverflowError
异常
> - 栈扩展失败
时,会抛出OutOfMemoryError
异常
Java堆
(Java Heap)
> - 虚拟机所管理的内存中最大的一块
> - Java堆
是被所有线程共享
的一块内存区域
> - 唯一的目的:存放对象示例
。
> - Java中 “几乎” 所有的对象实例都在这里分配内存;
> - 但是,由于现在技术发展,说 “Java对象示例都分配在堆上” 也渐渐变得不是那么绝对了。
> - Java堆
是垃圾收集器
管理的内存区域,也称“GC堆”
> - Java堆可以处于物理上不连续的内存空间,但在逻辑上它应该是被视为连续的
。
> - 如果在Java堆中没有内存完成实例分配
,并且Java堆也无法再扩展
时,Java虚拟机将会抛出OutOfMemoryError
异常
方法区
(Method Area)
> - 和 “Java堆” 一样,是被所有线程共享
的一块区域。
> - 在《Java虚拟机规范》中,把方法区描述为堆的一个逻辑部分
,但是它却有一个别名叫作 “非堆” ,目的是与Java堆
区分开来。
> - 如果方法区无法满足新的内存分配需求
时,将抛出OutOfMemoryError
异常
运行时常量池
(Running Constant Pool)
> - 运行时常量池
是方法区的一部分
> - 常量池表:用于存放编译期
生成的各种字面量
与字符引用
。
> - 这部分内容将在类加载后
存放到方法区的运行时常量池
中。
> - 运行时常量池
相对Class文件常量池
的一个重要特征是具备动态性
。
> - 当常量池无法再申请到内存
时,会抛出OutOfMemoryError
异常
直接内存
(Direct Memory)
> - 既不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。
> - 但是这部分内存区域也被频繁地使用,而且也可能导致OutOfMemoryError
异常出现
>
> > 1. 在JDK 1.4
中新加入了NIO(New Input/Output)
类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式
,它可以使用Native函数库
直接分配堆外内存
,然后通过一个存储在Java堆中的DirectByteBuffer对象
作为这块内存的引用
进行操作。这样能在一些场景中显著提高性能
,因为避免了在Java堆和Native堆中来回复制数据
。
> > 2. 在本机直接内存的分配不会受到Java堆
大小的限制,但是,既然是内存,则肯定还是会受到本机总内存
(包括RAM及SWAP区或者分页文件)的大小及处理器寻址空间
的限制。服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx
等参数信息,但经常会忽略掉直接内存
,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展
时出现OutOfMemoryError
异常。
OOM的error类型
java.lang.OutOfMemoryError: Java heap space
> - Java堆 内存溢出
,是最常见的一种情况。
>
> 原因:
>
> - 一般由于内存泄露
或者堆的大小设置不当
引起。
>
> 解决:
>
> - 对于内存泄露
:需要通过内存监控软件,查找程序中的泄露代码,
>
> - 对于堆大小
,可以通过虚拟机VM参数进行修改:
> - -Xms1024M -Xmx2048M
java.lang.OutOfMemoryError: PermGen space
> - 方法区
溢出
> - PermGen space
的全称是Permanent Generation space
(指内存的永久保存区域
)。
>
> 原因:
>
> - 加载了大量的Class(类)
> - 在单一的Tomcat实例下运行多个Web应用程序(大量色jsp页面)
> - 在运行的Tomcat实例中反复“热部署”Web应用程序
> - 采用cglib
等反射机制
> - 过多的常量也会导致方法区溢出,尤其是字符串
>
> 解决:
>
> - 修改方法区
的大小(缺省默认为64M):
> - -XX:PermSize=128M -XX:MaxPermSize=256M
java.lang.StackOverflowError
> - 不会抛出OOM Error
,但是也是比较常见的Java内存溢出
情况。
> - Java虚拟机栈
or本地方法栈
,在栈深度溢出(线程请求的栈深度大于虚拟机所允许的深度)
,将抛出StackOverflowError
异常
>
> 原因:
>
> - 最常见的:无限递归循环调用(死循环)
> - 栈深度溢出
> - 执行了大量方法,导致线程栈空间耗尽
> - 方法内声明了大量的局部变量
>
> 解决:
>
> - 通过程序抛出的异常堆栈,利用内存监控软件,查找程序中执行死循环的代码
> - 排查是否存在类之间的循环依赖
> - 设置JVM启动参数 -Xss
,增加线程栈内存空间
> - 线程栈的默认大小依赖于操作系统、JVM 版本和供应商
OOM分析
Heap Dump(堆转储文件)
是一个Java进程在某个时间点上的内存快照。
Heap Dump
是有着多种类型的。
不过总体上Heap Dump
在触发快照的时候都保存了Java对象
和类
的信息。
通常在写
Heap Dump
文件前会触发一次FullGC
,所以Heap Dump
文件中保存的是FullGC后留下
的对象信息。配置参数:
-XX:+HeapDumpOnOutOfMemoryError
,可以在发生OutOfMemoryError
后获取到一份HPROF
二进制Heap Dump
文件,生成的文件会直接写入到工作目录。
> 注意:该方法需要 JDK5
以上版本。
转存堆内存
信息后,需要对文件进行分析,可以使用以下工具,从而找到OOM
的原因:
JProfiler:IDEA继承了对应插件,详细参考 《Dump分析实战》
MAT(Memory Analyzer Tool):基于Eclipse RCP的内存分析工具。具体使用参考:http://www.eclipse.org/mat/
全部评论
(7) 回帖