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. 洛谷 P3431:[POI2005]AUT-The Bus(离散化+DP+树状数组)

    题目描述 The streets of Byte City form a regular, chessboardlike network - they are either north-south o ...

  2. Java初学者作业——编写Java程序,输出1~100之间能够同时被3和4整除的最大的五个数字。

    返回本章节 返回作业目录 需求说明: 编写Java程序,输出1-100之间能够同时被3和4整除的最大的五个数字. 实现思路: 声明变量count,用于存储满足条件的数据个数,设置初始值为0. 在区间1 ...

  3. Java面向对象笔记 • 【第2章 面向对象进阶】

    全部章节   >>>> 本章目录 2.1 成员变量 2.1.1 成员变量与局部变量的区别 2.1.2 成员变量的使用 2.1.3 实践练习 2.2 this关键字 2.2.1 ...

  4. Java_Swing中让窗口居中显示的方法(三种方法)

    方法一: int windowWidth = frame.getWidth(); // 获得窗口宽    int windowHeight = frame.getHeight(); // 获得窗口高 ...

  5. .net core的配置介绍(一):IConfiguration

    说到配置,绝大部分系统都会有配置,不需要配置的系统是非常少的,想想以前做.net 开发时,我们常常将配置放到web.config中,然后使用ConfigurationManager去读取. 初次接触到 ...

  6. netty系列之:netty对SOCKS协议的支持

    目录 简介 SocksMessage Socks4Message Socks5Message 总结 简介 SOCKS是一个优秀的网络协议,主要被用来做代理,它的两个主要版本是SOCKS4和SOCKS5 ...

  7. Lombok 安装配置及使用方法

    pom.xml 引入依赖 <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <!--Feb 0 ...

  8. html 基础 audio和video的基础使用

    基础标签 文本格式化标签: 标签 说明 <b></b>/<strong></strong> 加粗 <u></u>/<ins ...

  9. Python 安装第三方模块时 报Retrying(Retry(total=4, connect=None, read=None, redirect=None, status=None))...[WinError 10061]由于目标计算机积极拒绝,无法连接 错误

    今日在安装ddt模块时(Windows系统),cmd报了一个以前从未见过的错误,如下图所示: 经百度,知是镜像源的问题,将安装命令改成如下命令: pip install ddt -i https:// ...

  10. Selenium_界面的刷新、后退、前进操作(4)

    import time from selenium import webdriver driver = webdriver.Chrome() driver.maximize_window() driv ...