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. 教学日志:javaSE-循环语句

    一.while循环和do...while循环 /* while循环:先判断条件,再执行逻辑代码 四部分组成: 1.初始化:循环的初始化变量 2.条件判断:条件返回必须是true或false 3.循环体 ...

  2. 问题--ImportError: DLL load failed: 找不到指定的模块

    今天在运行别人的项目时出现了问题: ImportError: DLL load failed: 找不到指定的模块. 解决方法: 卸载后重新安装. 详情参考: Python报错:ImportError: ...

  3. 浏览器Cookie无法删除问题

    1.写Cookie时添加路径: //写cookies,设置过期时间为30天,并设置path为根目录 function setPathCookie(name,value) { var Days = 30 ...

  4. DFT, DHT, DCT, DST

    目录 基本 酉变换 others Fourier-related Transforms DFT DHT DCT 与DFT的联系 DST 与DFT的联系 Gonzalez R. C. and Woods ...

  5. 【MySQL作业】外连接查询——美和易思外连接查询应用习题

    点击打开所使用到的数据库>>> 1.使用左接获取所有客户的基本信息以及订购信息,要求输出客户姓名.电话.订单 ID 和下单时间. 由于需要获取所有客户的基本信息,如果采用左连接加以实 ...

  6. 每天学一点——python变量、常量与数字类型

    python变量.常量与数字类型 常量 (一句话能概括先讲它) 严格来讲,python中除了π与N就没有不变的量 所以,在python中我们识别常量是看它是否全大写(如下图) 变量 变量,顾名思义,就 ...

  7. CSS 基础 背景相关属性操作

    1.background-color:red : //设置背景颜色为红色,rgb(0,0,0)和transparent 均为透明颜色 2.background-image(可缩bgi写用tab键) 语 ...

  8. ASP.NET+MVC入门踩坑笔记 (一) 创建项目 项目配置运行 以及简单的Api搭建

    哈喽各位 我又回来了! 前段时间研究了下ASP.NET,刚开始也是随便找网上的各种教程来看,但是鉴于本人技术有限,还是走了相当长的一段弯路的.所以我写下了这篇文章.希望各位刚刚入坑的ASP.NET开发 ...

  9. Tool_BurpSuite安装和简单使用

    一.安装 1.检查Java环境 Burp Suite是用Java语言开发的,运行时依赖于JRE,因此需要先配置Java环境.在CMD中输入java -version 出现下图的结果,证明已配置Java ...

  10. centos7 安装locate

    使用locate my.cnf命令可以列出所有的my.cnf文件 yum  -y install mlocate 原因是安装完后没有更新库 更新库:updatedb