Java程序员必备:常见OOM异常分析
前言
放假这几天,温习了深入理解Java虚拟机的第二章, 整理了JVM发生OOM异常的几种情况,并分析原因以及解决方案,希望对大家有帮助。
Java 堆溢出
Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。
Java 堆溢出原因
无法在 Java 堆中分配对象
应用程序保存了无法被GC回收的对象。
应用程序过度使用 finalizer。
Java 堆溢出排查解决思路
1.查找关键报错信息,如
java.lang.OutOfMemoryError: Java heap space
2.使用内存映像分析工具(如Eclipsc Memory Analyzer或者Jprofiler)对Dump出来的堆储存快照进行分析,分析清楚是内存泄漏还是内存溢出。
3.如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,修复应用程序中的内存泄漏。
4.如果不存在泄漏,先检查代码是否有死循环,递归等,再考虑用 -Xmx 增加堆大小。
demo代码
package oom;
import java.util.ArrayList;
import java.util.List;
/*** JVM配置参数* -Xms20m JVM初始分配的内存20m* -Xmx20m JVM最大可用内存为20m* -XX:+HeapDumpOnOutOfMemoryError 当JVM发生OOM时,自动生成DUMP文件* -XX:HeapDumpPath=/Users/weihuaxiao/Desktop/dump/ 生成DUMP文件的路径*/
public class HeapOOM {static class OOMObject {}public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();//在堆中无限创建对象while (true) {list.add(new OOMObject());}}
}
运行结果
按照前面的排查解决方案,我们来一波分析。
1.查找报错关键信息
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
2. 使用内存映像分析工具Jprofiler分析产生的堆储存快照
由图可得,OOMObject这个类创建了810326个实例,是属于内存溢出,这时候先定位到对应代码,发现死循环导致的,修复即可。
栈溢出
关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;
如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError 异常。
栈溢出原因
在单个线程下,栈帧太大,或者虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出StackOverflowError 异常。
不断地建立线程的方式会导致内存溢出。
栈溢出排查解决思路
查找关键报错信息,确定是StackOverflowError还是OutOfMemoryError
如果是StackOverflowError,检查代码是否递归调用方法等
如果是OutOfMemoryError,检查是否有死循环创建线程等,通过-Xss降低的每个线程栈大小的容量
demo代码
package oom;
/*** -Xss2M*/
public class JavaVMStackOOM {private void dontStop(){while(true){}}public void stackLeakByThread(){while(true){Thread thread = new Thread(new Runnable(){public void run() {dontStop();}});thread.start();}}public static void main(String[] args) {JavaVMStackOOM oom = new JavaVMStackOOM();oom.stackLeakByThread();}
}
运行结果
1.查找报错关键信息
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
2.确定是创建线程导致的栈溢出OOM
Thread thread = new Thread(new Runnable(){public void run() {dontStop();}});
3.排查代码,确定是否显示使用死循环创建线程,或者隐式调用第三方接口创建线程(之前公司,调用腾讯云第三方接口,上传图片,遇到这个问题)
方法区溢出
方法区,(又叫永久代,JDK8后,元空间替换了永久代),用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。运行时产生大量的类,会填满方法区,造成溢出。
方法区溢出原因
使用CGLib生成了大量的代理类,导致方法区被撑爆
在Java7之前,频繁的错误使用String.intern方法
大量jsp和动态产生jsp
应用长时间运行,没有重启
方法区溢出排查解决思路
检查是否永久代空间设置得过小
检查代码是否频繁错误得使用String.intern方法
检查是否跟jsp有关。
检查是否使用CGLib生成了大量的代理类
重启大法,重启JVM
demo代码
package oom;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/*** jdk8以上的话,* 虚拟机参数:-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M*/
public class JavaMethodAreaOOM {public static void main(String[] args) {while (true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {public Object intercept(Object obj, Method method,Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});enhancer.create();}}static class OOMObject {}
}
运行结果
1.查找报错关键信息
Caused by: java.lang.OutOfMemoryError: Metaspace
2.检查JVM元空间设置参数是否过小
-XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
3. 检查对应代码,是否使用CGLib生成了大量的代理类
while (true) {...enhancer.setCallback(new MethodInterceptor() {public Object intercept(Object obj, Method method,Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});enhancer.create();}
本机直接内存溢出
直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。但是,这部分内存也被频繁地使用,而且也可能导致OOM。
在JDK1.4 中新加入了NIO(New Input/Output)类,它可以使用 native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
直接内存溢出原因
本机直接内存的分配虽然不会受到Java 堆大小的限制,但是受到本机总内存大小限制。
直接内存由 -XX:MaxDirectMemorySize 指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。
NIO程序中,使用ByteBuffer.allocteDirect(capability)分配的是直接内存,可能导致直接内存溢出。
直接内存溢出
检查代码是否恰当
检查JVM参数-Xmx,-XX:MaxDirectMemorySize 是否合理。
demo代码
package oom;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
/*** -Xmx256m -XX:MaxDirectMemorySize=100M*/
public class DirectByteBufferTest {public static void main(String[] args) throws InterruptedException{//分配128MB直接内存ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*128);TimeUnit.SECONDS.sleep(10);System.out.println("ok");}
}
运行结果
ByteBuffer分配128MB直接内存,而JVM参数-XX:MaxDirectMemorySize=100M指定最大是100M,因此发生直接内存溢出。
ByteBuffer bb = ByteBuffer.allocateDirect(1024*1024*128);
GC overhead limit exceeded
这个是JDK6新加的错误类型,一般都是堆太小导致的。
Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。
解决方案
检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。
检查JVM参数-Xmx -Xms是否合理
dump内存,检查是否存在内存泄露,如果没有,加大内存。
demo代码
package oom;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*** JVm参数 -Xmx8m -Xms8m*/
public class GCoverheadTest {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < Integer.MAX_VALUE; i++) {executor.execute(() -> {try {Thread.sleep(10000);} catch (InterruptedException e) {//do nothing}});}}
}
运行结果
实例代码使用了newFixedThreadPool线程池,它使用了无界队列,无限循环执行任务,会导致内存飙升。因为设置了堆比较小,所以出现此类型OOM。
总结
本文介绍了以下几种常见OOM异常
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: unable to create new native thread
java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: Direct buffer memory
java.lang.OutOfMemoryError: GC overhead limit exceeded
希望大家遇到OOM异常时,对症下药,顺利解决问题。同时,如果有哪里写得不对,欢迎指出,感激不尽。
参考与感谢
JVM系列之实战内存溢出异常
JVM 发生 OOM 的 8 种原因、及解决办法
NIO-直接内存
《深入理解Java虚拟机》
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️
Java程序员必备:常见OOM异常分析相关推荐
- Java 程序员必备的 15 个框架,前 3 个地位无可动摇!
2019独角兽企业重金招聘Python工程师标准>>> Java 程序员方向太多,且不说移动开发.大数据.区块链.人工智能这些,大部分 Java 程序员都是 Java Web/后端开 ...
- Java程序员必备的几款开发工具,高效才是硬道理!
作为一名优秀的Java程序员,怎能没有几款得心应手的高效开发工具呢!市面上类库.工具千千万,下面我就给大家推荐几款高效的Java开发工具. 一.Eclipse Eclipse做为一款开发源代码的Jav ...
- Java程序员必备的几款开发工具
工欲善其事,必先利其器.作为一名优秀的Java程序员,怎能没有几款得心应手的高效开发工具呢!市面上类库.工具千千万,下面就给大家推荐几款高效的Java开发工具. 1.UItraEdit UltraEd ...
- Java程序员必备的10个大数据框架
当今IT开发人员面对的最大挑战就是复杂性,硬件越来越复杂,OS越来越复杂,编程语言和API越来越复杂,我们构建的应用也越来越复杂.根据外媒的一项调查报告,小千列出了Java程序员在过去12个月内一直使 ...
- B站疯传|200个Java程序员必备词汇
相信很多学生在学习英语的时候都有这样那样的问题,因此也困扰着太多想要学习编程的小伙伴. 其实学习编程,常用的单词就那么多,只要把必备的单词学会,你的代码也能写的很6,编程和英语的关系就是这么简单纯粹. ...
- 第一百五十期:Java程序员必备:异常的十个关键知识点
总结了Java异常十个关键知识点,面试或者工作中都有用哦,加油.异常是指阻止当前方法或作用域继续执行的问题.比如你读取的文件不存在,数组越界,进行除法时,除数为0等都会导致异常. 前言 总结了Jav ...
- Java程序员必备:异常的十个关键知识点
前言 总结了Java异常十个关键知识点,面试或者工作中都有用哦,加油. 一. 异常是什么 异常是指阻止当前方法或作用域继续执行的问题.比如你读取的文件不存在,数组越界,进行除法时,除数为0等都会导致异 ...
- 程序流程图_干货收藏 | Java 程序员必备的一些流程图
阅读本文大概需要 6 分钟. 转载自:https://juejin.im/post/5d214639e51d4550bf1ae8df 1.Spring 的生命周期 Spring 作为当前 Java 最 ...
- Java 程序员必备的一些流程图
1.spring的生命周期 2.TCP三次握手,四次挥手 3.线程池执行流程图 4.JVM内存结构 5.Java内存模型 6.springMVC执行流程图 7.JDBC执行流程 8.spring cl ...
最新文章
- 51年被发现9次,陶哲轩证明的公式成了重复造轮子?事情并没有这么简单
- Dataset之Handwritten Digits:Handwritten Digits(手写数字图片识别)数据集简介、安装、使用方法之详细攻略
- Python字节到大整数的打包与解包
- 在苏州的一个超级棒的事情
- Xposed源码剖析——Xposed初始化
- typescript 类型映射 (ReadOnly、Partial)
- js中startsWith 函数不能在任何浏览器兼容的问题
- mysql编号用什么类型_mysql 之编码配置、引擎介绍、字段操作、数据类型及约束条件...
- Eclipse Java Build Path详解
- 报错:使用java api连接redis集群时报错 READONLY You can't write against a read only slave....
- 设备密码的设置以及遗忘重设置
- Grunt usemin前端自动化打包流程
- python jieba库的使用
- php 数字转换为字符串,PHP-将数字转换为Excel的字符串
- 老年人大学计算机课程,老人学电脑(入门级)
- vue3 出现 Component inside <Transition> renders non-element root node that cannot be animated.
- 在opencv中如何生成灰度图
- android openCV检测图像的基本特征,包括Canny边缘检测、Harris角点检测、霍夫直线检测-基于Android studio
- 闲置电脑搭建一台linux服务器,在局域网内访问
- CC-Proxy配置网络代理服务器
热门文章
- php网站不能访问,php怎么不登录不能访问
- 计算机指令要素,【计算机系统】CPU指令执行流程与指令流水线原理
- 异常来自 hresult:0x800a03ec_肺癌早期不是悄无声息,一旦出现这3个“异常”,十有八九是肺癌...
- 数据结构之外部排序:置换-选择排序
- 超简单的Tomcat安装过程
- Linux报错:/etc/sudoers is world writable
- Existing lock /var/run/yum.pid: another copy is running as pid
- PyMongo官方文档翻译——VNPY
- webServlet(/) 和 webServlet(/*) 的区别
- Oracle数据库导入导出 imp/exp备份还原