1. 前言

工作中有可能遇到 java.lang.OutOfMemoryError: Java heap space 内存溢出异常, 本文提供一些内存溢出的分析及解决问题的思路.

常见异常如下:

2022-01-31 16:07:29.639 ERROR 1981 --- [http-nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space] with root cause

java.lang.OutOfMemoryError: Java heap space

2. 内存溢出的问题

解决问题之前先来分析一下为什么会出现内存溢出的问题.

有两种可能性:

一种是应用有问题, 本该回收的内存没有进行回收导致的内存溢出, 这种情况就需要修改代码了.

第二种情况则是服务器资源不够或JVM参数设置过小导致的内存溢出,这种情况需要更换服务器或修改启动参数

我们可以使用对应的工具或命令来定位到问题, 然后分析是哪种情况, 最后再解决问题.

3. 场景模拟

通过下列代码来模拟内存溢出的情况:


// 通过无限创建自定义对象模拟内存溢出的场景
@GetMapping("oom")
public void oom(){
while(true){
CustomObj customObj = new CustomObj();
}
}

/**
* @author liuboren
* @Title: 自定义对象
* @Description: 创建该对象用于模拟OOM场景
* @date 2022/1/30 16:55
*/
public class CustomObj { // 利用numbers成员变量尽可能更快的用光内存
private int[] numbers = new int[10000000]; }

再将应用的启动JVM参数设置为 -Xms70m -Xmx70m即可.

通过访问/oom的接口, 很快程序就会报

2022-01-31 16:07:29.639 ERROR 1981 --- [http-nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space] with root cause

java.lang.OutOfMemoryError: Java heap space

4. 分析的方法

问题已经出来了, 我们可以通过一下几种方法来定位分析问题:

  • 查看日志
  • 使用jmap命令
  • 分析堆转储文件
  • 利用arthas进行分析
  • 使用jstat命令

4.1 日志分析

通过查看对应的日志可以很清晰的定位到错误:

java.lang.OutOfMemoryError: Java heap space
at com.example.demo.entity.CustomObj.<init>(CustomObj.java:11) ~[demo.jar:0.0.1-SNAPSHOT]
at com.example.demo.controller.TestController.oom(TestController.java:36) ~[demo.jar:0.0.1-SNAPSHOT]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]

可以看到TestController类中的oom方法,里面的CustomObj对象造成了内存溢出.

这时候查看对应的代码进行分析:


@GetMapping("oom")
public void oom(){
while(true){
CustomObj customObj = new CustomObj();
}
}

这个例子是我们使用了while(true) 无限的去创造对象, 所以造成的内存溢出, 我们修改对应的代码即可.

如果程序正常的情况下,就要考虑修改JVM启动参数调整堆空间或者将应用放到内存更大的服务器即可.

4.2 jmap

通过日志只可以定位到对应的代码位置,如果我们想看内存中到底是什么对象占用的空间比较多, 这时候就可以使用jmap命令了

使用下列命令可以查看内存中已产生对象的实例数和大小

jmap -histo pid |head -n 20

-histo参数代表所有的对象,包括已经垃圾回收掉的对象, 如果只想看目前存活的对象可以增加:live参数:

jmap -histo:live pid  |head -n 20

至于head -n 20 则代表输出排名前20的数据, 如果不加这个参数那么展示的数据就太多了, 不利于排查问题.

然后看实际效果:

通过上图可以看出int 类型占了 40294040bytes 差不多38mb.这是因为我的测试类中的CustomObj对象 new 了一个int数组导致的.


**
* @author liuboren
* @Title: 自定义对象
* @Description: 创建该对象用于模拟OOM场景
* @date 2022/1/30 16:55
*/
public class CustomObj { private int[] numbers = new int[10000000]; }

使用jmap命令可以快速的查看内存中的对象的实例及占用的大小, 但是缺点就是显示的不是那么直观, 并且如果应用重启了那么也就无法查看了.

所以为了避免这种情况,可以通过生成堆转储文件来进行分析.

4.3 堆转储文件分析

刚刚说了使用jmap进行内存分析的缺点, 现在看看如何使用堆转储文件

生成堆转储文件有3中方式:

  1. 启动时添加 JVM参数
-XX:+HeapDumpOnOutOfMemoryError参数表示当JVM发生OOM时,自动生成DUMP文件。
  1. 使用jmap
jmap -dump:live,format=b,file=heap.bin <pid>

  1. 使用arthas
heapdump

生成堆转储文件之后, 需要dump到本地进行分析

分析堆转储文件的三种方式:

  1. jhat
jhat -port 8000 java_pid2162.hprof

jhat默认端口是7000, 如果有端口占用的情况, 可以通过 -port 参数替换默认端口

  1. visualVm
JVisualVm
  1. Eclipse Memory Analyzer

下面看看实际的效果:

  • jhat

利用jhat分析堆转储文件的可视化效果不是那么友好, 不重点介绍了, 下图是可以通过查询语句来显示大于50k的对象.

  • VisualVm

执行JVisualVm命令启动客户端后, 导入堆转储文件:



显示基本的信息及执行错误的线程:



点击线程可以查看是执行的哪段代码:



对象的类型、实例数及大小



同样支持利用语句查询内存中的对象, 下面是查询内存中大于5mb的对象

可以看到VisualVm的显示界面是相当友好的, 并且功能十分的强大,可以查看是哪个线程执行的哪段代码,同时也可以查看对象的类型和大小. 推荐使用VisualVm

  • Eclipse Memory Analyzer

    Eclipse Memory Analyzer 的功能同样很强大,就是需要额外的装一些东西, 有兴趣的朋友可以参考下面的链接 , 不多做介绍了:

    链接

  • 使用对转储文件的缺点

堆转储文件的优势是展示界面友好, 并且不会因为应用重启而丢失, 但是它最大的问题就是, 因为随着应用的运行对转储文件的体积也在不断增加, 小则几g大则几十上百g. 无论是将文件dump到本地然还是进行分析都是非常耗时的.

4.4 arthas

Arthas 是Alibaba开源的Java诊断工具. 非常好用, 不了解的同学自行百度.

官方文档

下面正文

使用arthas的 jvm和 dashboard命令 可以查看jvm的情况, 并且使用heapdump也可以生成堆转储文件

jvm命令可以看到 使用的jvm 参数 、使用的垃圾回收器、垃圾回收的时间、新生代老年代的空间、堆内存的使用情况等等

启动参数:



垃圾回收情况:



内存使用情况:

dashboard 可以看到线程执行情况及内存中各个区域的大小及使用情况:

使用heapdump命令可以生成堆转储文件

4.5 jstat

jstat也是jdk自带的小工具, 功能非常的强大,可以查看垃圾会回收的次数及时间, 查看新生代老年代的剩余空间等等.

命令如下:

jstat -gcutil  pid  1000

1000是毫秒数,代表每1000毫秒输出一次

我使用jstat命令主要是查看应用的full gc的情况, 如果出现频繁的full gc 这时候就很有必要对程序进行调优了.

频繁full gc 的两个调整思路:

  1. 尝试调整新生代和老年代的比例, 将新生代的比例调大,这样做的原因在于动态对象年龄判定的机制(同年龄的对象的大小超过整个Survivor区的一半,大于等于这个年龄的对象都会被放入老年代)
  2. 尝试更换垃圾回收器(例如将cms更换为 g1)

总结

以上就是我个人的一些分析解决OOM的一些经验之谈, 如果应用发生了OOM的异常, 我们可以通过以下几个步骤尝试分析解决:

  1. 查看日志, 可以定位到对应的代码段, 然后进行分析是否是应用有问题, 有的话进行修改
  2. 通过jmap命令查看内存中的对象是什么占用的比较多,是否有需要优化的对象
  3. 添加对应的jvm参数可以在发生oom的时候生成堆转储文件, 然后使用对应的工具或命令来进行分析, 这样做的好处在于就算应用重启了依然有迹可循,然后解决问题
  4. 使用arthas进行分析. arthas不得不说非常的强大, 线上问题排查的利器. 谁用谁知道.
  5. 使用jstat分析gc的情况和耗时,如果有频繁的full gc,也许要进行解决

参考连接

jstat命令详解

堆转储文件分析

arthas官方文档

JVM 问题分析思路的更多相关文章

  1. 【jvm】09-full gc分析思路

    [jvm]09-full gc分析思路 欢迎关注b站账号/公众号[六边形战士夏宁],一个要把各项指标拉满的男人.该文章已在github目录收录. 屏幕前的大帅比和大漂亮如果有帮助到你的话请顺手点个赞. ...

  2. 全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)

    今天,又是干货满满的一天.这是全网最硬核 JVM 系列的开篇,首先从 TLAB 开始.由于文章很长,每个人阅读习惯不同,所以特此拆成单篇版和多篇版 全网最硬核 JVM TLAB 分析(单篇版不包含额外 ...

  3. Java应用性能瓶颈分析思路

    1 问题描述 因产品架构的复杂性,可能会导致性能问题的因素有很多.根据部署架构,大致的可以分为应用端瓶颈.数据库端瓶颈.环境瓶颈三大类.可以根据瓶颈的不同部位,选择相应的跟踪工具进行跟踪分析. 应用层 ...

  4. jvm调优思路及调优案例

    jvm调优思路及调优案例 ​ 我们说jvm调优,其实就是不断测试调整jvm的运行参数,尽可能让对象都在新生代(Eden)里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时 ...

  5. CPU利用率异常的分析思路和方法交流探讨

    CPU利用率异常的分析思路和方法交流探讨在生产运行当中,经常会遇到CPU利用率异常或者不符合预期的情况,此时,往往暗示着系统性能问题.那么究竟是核心应用的问题?是监控工具的问题?还是系统.硬件.网络层 ...

  6. JVM性能分析与优化

    JVM性能分析与优化: http://www.docin.com/p-757199232.html

  7. enode框架step by step之框架要实现的目标的分析思路剖析1

    enode框架step by step之框架要实现的目标的分析思路剖析1 enode框架系列step by step文章系列索引: 分享一个基于DDD以及事件驱动架构(EDA)的应用开发框架enode ...

  8. so静态分析进阶练习——一个CreakeMe的分析思路

    i春秋作家:HAI_ 原文来自:https://bbs.ichunqiu.com/thread-41371-1-1.html 说明 拿到一个CreakeMe,写一个分析思路.CreakMe主要是对.s ...

  9. 多线程_java多线程环境下栈信息分析思路

    导读:Java多线程开发给程序带来好处的同时,由于多线程程序导致的问题也越来越多,而且对问题的查找和分析解决对于菜鸟程序原来是是件头疼的事.下面我就项目中使用多线程开发程序过程中遇到的问题做详细的分析 ...

随机推荐

  1. 由于ios由UIWebView换成了WKWebview内核后导致webview请求接口文件上传,后台接收不到文件

    2020年4月起App Store将不再接受使用UIWebView的新App上架.2020年12月起将不再接受使用UIWebView的App更新. 解决后台文件接收不到的问题 function GLA ...

  2. Capstone CS5263|DP转HDMI 4K60HZ转换芯片|CS5263芯片|替代PS176芯片

    CS5263是一款DP转HDMI 4K60HZ音视频转换器芯片,不管在功能特性还是应用上都是可以完全替代兼容PS176.PS176是一个Display Port 1.2a到HDMI 2.0协议转换器, ...

  3. 从JVM设计角度解读Java内存模型

    第十六章:Java内存模型 本文我们将重点放在Java内存模型(JMM)的一些高层设计问题,以及JMM的底层需求和所提供的保证,还有一些高层设计原则背后的原理. 例如安全发布,同步策略的规范以及一致性 ...

  4. Kafka版本介绍Version2.4.0

    1.说明 Kafka的版本从0.11.0.X到1.0.X, 再到2.0.X大版本, 其实没有经过几个版本, 只是版本号变化较大. 2.最新发布版本 截止本文章2020年2月22号发布时, Kafka ...

  5. 在B站学Java

    大家好,我是大彬~ 众所周知,B站是用来搞学习的,对于学编程的小伙伴来说,B站有着非常丰富的学习资源.今天给大家分享一些质量比较高的Java学习视频,希望对大家有帮助! Java基础 首先是Java基 ...

  6. SQLAlchemy(十)

    ORM操作在实际项目中的应用非常多,涉及到的框架也是根据不同的项目有不同的处理模块,不过操作流程和步骤都是大同小异基本没有什么太大变化,唯一需要注意的就是在实际操作过程中你要使用的ORM框架的处理性能 ...

  7. zabbix监控图形中文乱码的解决方法

    问题描述: 最近搭建了一套zabbix,当我把语言切换到中文的时候,发现监控的图形界面中一些中文参数乱码,但是图形界面在英文环境下完全没有乱码问题.如下图(中文界面): 解决方法: 解决方法有两种,方 ...

  8. xxe-lab学习

    0x00 前言 xxe-lab是一个一个包含php,java,python,C#等各种语言版本的XXE漏洞Demo这里附上下载链接https://github.com/c0ny1/xxe-lab我们用 ...

  9. 深入浅出 CSS 动画

    本文将比较全面细致的梳理一下 CSS 动画的方方面面,针对每个属性用法的讲解及进阶用法的示意,希望能成为一个比较好的从入门到进阶的教程. CSS 动画介绍及语法 首先,我们来简单介绍一下 CSS 动画 ...

  10. Android官方文档翻译 十五 3.3Supporting Different Platform Versions

    Supporting Different Platform Versions 支持不同的平台版本 This lesson teaches you to 这节课教给你 Specify Minimum a ...