当Java程序中堆内存使用率一直很高,且不下降时,如何定位是那一段程序出现了问题?

1 Demo程序

程序的主要思路就是,每发送一次请求,就会往ConcurrentHashMap中put一个value长度为1k的KV对。这样随着请求的不断增加,势必会造成程序中的内存资源被耗尽,具体表现就是Java程序的老年代使用率超过90%,程序出现卡死情况。

主要代码如下:

//controller
@RestController
@RequestMapping("/test")
public class TestController {@Autowiredprivate TestService service;@RequestMapping("/test")public String test() {String result = service.test();return result;}
}//service
@Service
public class TestService {private ConcurrentHashMap<Integer, byte[]> map = new ConcurrentHashMap<>();public String test() {this.put();return "OK....." + this.map.size();}public void put(){while(true) {int key = map.size();if (map.get(key) == null){if (map.putIfAbsent(key, new byte[1024]) == null){break;}}}}
}

现在就基于本程序,分析到底是哪一段代码会造成内存资源被超支使用。

把程序打成test.jar包,执行java -Xms64m -Xmx64m -jar test.jar,给该Java程序分配64m的堆内存,分配这么小的目的就是为了尽快出现问题。程序启动之后,不断的往该程序发送请求,直至内存资源被耗尽。

2 出现问题时的内存情况

#使用jmap -heap {pid}可以查看对应Java程序的内存使用情况
jmap -heap 147365#结果
Attaching to process ID 147365, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.231-b11using thread-local object allocation.
Parallel GC with 8 thread(s)Heap Configuration:MinHeapFreeRatio         = 0MaxHeapFreeRatio         = 100MaxHeapSize              = 67108864 (64.0MB)NewSize                  = 22020096 (21.0MB)MaxNewSize               = 22020096 (21.0MB)OldSize                  = 45088768 (43.0MB)NewRatio                 = 2SurvivorRatio            = 8MetaspaceSize            = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize         = 17592186044415 MBG1HeapRegionSize         = 0 (0.0MB)Heap Usage:
PS Young Generation
Eden Space:capacity = 9437184 (9.0MB)used     = 9421392 (8.984939575195312MB)free     = 15792 (0.0150604248046875MB)99.83266194661458% used
From Space:capacity = 6291456 (6.0MB)used     = 0 (0.0MB)free     = 6291456 (6.0MB)0.0% used
To Space:capacity = 6291456 (6.0MB)used     = 0 (0.0MB)free     = 6291456 (6.0MB)0.0% used
PS Old Generationcapacity = 45088768 (43.0MB)used     = 45054616 (42.967430114746094MB)free     = 34152 (0.03256988525390625MB)99.92425608080487% used13877 interned Strings occupying 1975544 bytes.

从jmap的执行结果中可以看到,尤其是老年代内存使用率几乎达到了100%。

3 分析

为了了解对象在内存中的具体情况,可以借助于jmapMAT来进行分析。MAT是Eclipse开发的Java内存分析工具,可以基于该工具打开jmap导出的内存文件,提取其中的内存信息,以一种非常友好的方式呈现给用户。

首先通过命令jmap -dump:live,format=b,file=test.hprof 147365导出内存快照文件,后缀名需要是hprof

接下来,去MAT官网下载MAT工具,解压可用。Windows环境下,双击{MAT_HOME}下的MemoryAnalyzer.exe文件即可,如果导出的hprof文件很大,MAT默认的内存大小,不足以支撑打开改文件,可以在MemoryAnalyzer.ini中修改内存配置。

通过MAT打开hprof文件之后,观察各个模块:

3.1 Top consumers模块

从这个图中可以看出来,占用内存最多的对象是ConcurrentHashMap对象,引用的对象总大小达40M。而整个程序的堆内存才64M,因此此时可以推测极有可能是该对象导致的问题。

3.2 Histogram模块

该模块主要展示内存中对象的详细情况,包括对象所属的类,对象个数,对象自身的大小以及引用的对象的大小

这里我按照Retained Heap降序显示,即按照引用对象的大小来进行排序,可以发现TestService对象在内存中仅有一个,且这个类是我们自己写的,但是它引用的对象大小达40M。有此可以推测是该类存在问题。

在分析的时候,要优先考虑我们自己编写的代码可能存在问题。

3.3 List Objects

猜测TestService类存在问题,那么右击该类,查看这个类的list objects情况。

点击incoming reference,可以看到如下情况:

从图中可以看出来在TestService类中确实引用ConcurrentHashMap,再结合Top consumers模块中占内存最多的对象是ConcurrentHashMap,因此基本可以确定是TestService中引用的ConcurrentHashMap存在问题。

3.4 程序分析
@Service
public class TestService {private ConcurrentHashMap<Integer, byte[]> map = new ConcurrentHashMap<>();public String test() {this.put();return "OK....." + this.map.size();}public void put(){while(true) {int key = map.size();if (map.get(key) == null){if (map.putIfAbsent(key, new byte[1024]) == null){break;}}}}
}

TestService中确实有引用ConcurrentHashMap,再结合代码分析,TestService对象为所有线程共享,其成员变量map对象也为所有线程所共享,因此随着不断有请求的到来,map中添加的KV对也就越来越多,必然会导致内存被占满。

至此问题分析完毕。

Java程序堆内存使用率很高的一般分析思路相关推荐

  1. java 64位 默认分配内存大小_查看你机器中Java程序堆内存的默认初始大小和最大大小...

    很多时候,我们运行的Java程序并没有设定堆的内存限制参数,正常来说可以有两个参数来指定初始分配的堆内存和堆内存的最大值,分别为: -Xmx 用来设置你的应用程序(不是JVM)能够使用的最大内存数(相 ...

  2. 宝塔可以修改服务器内存限制吗,宝塔内存使用率很高的解决方法 cpu过高这样做!...

    不得不说,宝塔面板还是比较好用的,尤其是在用户体验方面做得不错,值得站长朋友们使用.倡萌会继续发布使用宝塔面板的一些技巧文章,欢迎继续关注WordPress大学. 开启监控功能 监控功能对于了解服务器 ...

  3. Java 占用CPU使用率很高的分析

    前几天在测试服务器上发现Java进程的CPU使用率暴高,为了分析解决该问题,把过程记录如下: 1. 先找到Java的进程号 Linux下: 用top命令查看所有进程,可以明显看到Java的,因为CPU ...

  4. 简述JAVA中堆内存与栈内存的区别

    Java把内存划分成两种:一种是栈内存,一种是堆内存. 一.栈内存 存放基本类型的变量,对象的引用和方法调用,遵循先入后出的原则. 栈内存在函数中定义的"一些基本类型的变量和对象的引用变量& ...

  5. 06 | 案例篇:系统的 CPU 使用率很高,但为啥却找不到高 CPU 的应用?

    上一节我讲了 CPU 使用率是什么,并通过一个案例教你使用 top.vmstat.pidstat 等工具,排查高 CPU 使用率的进程,然后再使用 perf top 工具,定位应用内部函数的问题.不过 ...

  6. 【Java 面向对象】基础、Java程序的内存划分、嵌套类(内部类、静态嵌套类)、局部类、接口的升级问题(默认方法、静态方法)、instanceof

    面向对象 对象的内存 复杂对象的内存 对象数组的内存 思考:方法存储在哪里? Java程序的内存划分 this.super 注解(Annotation) 访问控制(Access Control) to ...

  7. Java程序员找工作很难吗?可能没有get这些内容

    Java程序员找工作很难吗?可能没有get这些内容 五分钟阅读下方文章 经常面试一些候选人,整理了下我面试使用的题目,陆陆续续整理出来的题目很多,所以每次会抽一部分来问.答案会在后面的文章中逐渐发布出 ...

  8. Java的堆内存和栈内存

    一.Java的堆内存和栈内存 Java把内存划分成两种:一种是堆内存,一种是栈内存. 堆:主要用于存储实例化的对象,数组.由JVM动态分配内存空间.一个JVM只有一个堆内存,线程是可以共享数据的. 栈 ...

  9. java non-heap_Java堆内存Heap与非堆内存Non-Heap简介和设置

    Java 开发对JVM(Java虚拟机)的了解很有必要,网上看到,收集整理转载一下,方便日后的懒人计划 堆(Heap)和非堆(Non-heap)内存 按照官方的说法:"Java 虚拟机具有一 ...

最新文章

  1. Java线程同步的一些例子
  2. easyui numberbox一些常用属性,方法
  3. 【渝粤教育】广东开放大学 劳动人事争议处理法 形成性考核 (51)
  4. tcp段重组--suricata实现
  5. 设计模式(二十三)—— 模板方法
  6. flume java 安装部署_[Hadoop] Flume安装部署与简单使用
  7. 动态规划之矩阵连乘问题详细解读(思路解读+填表+代码)
  8. java overload_java之方法重载(overload)
  9. 用最简单的方法解决:linux系统重启网络delaying initialization错误
  10. [面试题]1000瓶毒药里面只有1瓶是有毒的,问需要多少只老鼠才能试出那瓶有毒。
  11. 七夕表白攻略:原来数学才是世界上最浪漫的学科!
  12. 【android】高仿京东商城App,集成react-native热更功能
  13. HC-05蓝牙AT指令无反应问题
  14. xp远程linux打印,在Ubuntu下访问xp打印机
  15. 哪些编程器可以做丰田智能钥匙OBD全丢?
  16. windows下用Python把pdf文件转化为图片(png高清)
  17. 工程导论-----工程,技术与工程师
  18. (9.26更新 老机福音,再创经典)Ghost_XP_战神 V9.5 老机优化版
  19. 系统分析与设计--学习笔记1
  20. 精灵图,背景图缩放。盒子阴影,

热门文章

  1. 罗马仕php30pro能边充边玩吗,surfacepro 可以边充电边玩吗?
  2. 修为进阶——二维数组
  3. STM32按键消抖的几种实现方式-STM32 Button Debouncing
  4. 2021-09-10 java 读取音频/视频 文件时长
  5. 弹跳式ppt模板动态目录怎么制作?
  6. OCP-V13-708
  7. Ueditor在vue2项目中的使用,Ueditor使用教程
  8. 公有链、私有链、联盟链的特点
  9. Selenium3 Python WebDriver API源码探析(19)加载FireFox用户配置文件
  10. linux mkfifo 函数,mkfifo - Linux C 函数 使用手册