简单的springboot应用部署后内存占用量过大问题排查

1.问题背景

需要部署一个演示环境。所有组件都要部署到一台服务器,采用Docker容器部署,发现多个简单的springboot应用占用内存高达2G,后续的应用因为内存不足就部署不了了。排查下内存占用大的原因:

docker stats命令:

2.排查步骤:

不想看过程的可以直接查看文末总结

jstats查下应用堆内存情况:

bash 复制代码
[root@968204e40b opt]# jstat -gc 1 1000 10
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
27136.0 26624.0  0.0    0.0   2508800.0 1676722.1  320512.0   23699.8   59160.0 56533.7 8320.0 7819.9     13    0.414   3      0.280
27136.0 26624.0  0.0    0.0   2508800.0 1676722.1  320512.0   23699.8   59160.0 56533.7 8320.0 7819.9     13    0.414   3      0.280
27136.0 26624.0  0.0    0.0   2508800.0 1676722.1  320512.0   23699.8   59160.0 56533.7 8320.0 7819.9     13    0.414   3      0.280

EC EU区居然比OC OU区还大,青年代大于老年代 也就是说青年代没有设置或者设置的过大了。青年代过大触发GC频率过低。造成大量垃圾对象堆积在青年代不回收。而Eden区多次扩容导致。

NGCMN:新生代最小容量

NGCMX:新生代最大容量

NGC:当前新生代容量

S0CMX:最大幸存1区大小

S0C:当前幸存1区大小

S1CMX:最大幸存2区大小

S1C:当前幸存2区大小

ECMX:最大伊甸园区大小

EC:当前伊甸园区大小

YGC:年轻代垃圾回收次数

FGC:老年代回收次数

jmap查看应用内存详情:

bash 复制代码
-- 问题发现 eden区内存大
[root@576aea1126d9 opt]# jmap -heap 1
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.191-b12

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 8317304832 (7932.0MB)
   NewSize                  = 173015040 (165.0MB)
   MaxNewSize               = 2772434944 (2644.0MB)
   OldSize                  = 347078656 (331.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 2402811904 (2291.5MB)
   used     = 1686518448 (1608.3893280029297MB) #应用刚启动 eden区就使用了1.6G内存  
   free     = 716293456 (683.1106719970703MB)
   70.18936626676542% used
From Space:
   capacity = 27262976 (26.0MB)
   used     = 0 (0.0MB)
   free     = 27262976 (26.0MB)
   0.0% used
To Space:
   capacity = 27787264 (26.5MB)
   used     = 0 (0.0MB)
   free     = 27787264 (26.5MB)
   0.0% used
PS Old Generation
   capacity = 350224384 (334.0MB)
   used     = 25793584 (24.598678588867188MB)
   free     = 324430800 (309.4013214111328MB)
   7.364873829002152% used

27924 interned Strings occupying 2735400 bytes.

查看启动命令:

docker run -d --cap-add=SYS_PTRACE -e JAVA_OPTS=' -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -Xms128m -Xmx128m -Xmn32m -Xss256k -XX:SurvivorRatio=6 -XX:+UseConcMarkSweepGC -Djava.net.preferIPv4Stack=true -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom -Dlog4j2.formatMsgNoLookups=true' 【镜像:版本】

--cap-add=SYS_PTRACE 用来解决docker容器内不能运行 jmap命令

说明启动参数:JAVA_OPTS 并没有生效。

一顿百度加实验后只有:将JVM参数加入Dockerfile中才生效

bash 复制代码
FROM med/oraclejdk:8u191
ADD hcp-terminal-app-1.0.0.jar /opt/app.jar
WORKDIR /opt
EXPOSE 8080
ENTRYPOINT ["java", "-jar","-XX:MaxMetaspaceSize=256m","-Xms128m","-Xmx128m","-Xmn32m","-XX:SurvivorRatio=6","-Dlog4j2.configuration=/opt/log4j2.properties","app.jar","--spring.config.location=/opt/application.yaml"]

重新构建镜像后启动

修改参数后内存使用量情况:

bash 复制代码
[root@15972b64bff3 opt]# jps
1 jar
205 Jps
[root@15972b64bff3 opt]# jstat -gc 1 1000 2
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
1536.0 1536.0 256.0   0.0   29696.0   5130.0   98304.0    76430.8   83712.0 79187.7 11520.0 10663.5    406    1.784   3      0.329    2.113
1536.0 1536.0 256.0   0.0   29696.0   5130.0   98304.0    76430.8   83712.0 79187.7 11520.0 10663.5    406    1.784   3      0.329    2.113

#eden区使用量[EU]下去了,OU使用量也是合理的

[root@15972b64bff3 opt]# jmap -heap 1
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.191-b12

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 134217728 (128.0MB)
   NewSize                  = 33554432 (32.0MB)
   MaxNewSize               = 33554432 (32.0MB)
   OldSize                  = 100663296 (96.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 6
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 260046848 (248.0MB)
   MaxMetaspaceSize         = 268435456 (256.0MB)
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 30408704 (29.0MB)
   used     = 7501416 (7.153907775878906MB)
   free     = 22907288 (21.846092224121094MB)
   24.668647503030712% used
From Space:
   capacity = 1572864 (1.5MB)
   used     = 753664 (0.71875MB)
   free     = 819200 (0.78125MB)
   47.916666666666664% used
To Space:
   capacity = 1572864 (1.5MB)
   used     = 0 (0.0MB)
   free     = 1572864 (1.5MB)
   0.0% used
PS Old Generation
   capacity = 100663296 (96.0MB)
   used     = 71246592 (67.946044921875MB)
   free     = 29416704 (28.053955078125MB)
   70.77713012695312% used

27238 interned Strings occupying 2666864 bytes.

至此:问题解决

3.总结:

问题排查步骤:

  1. docker stats 命令查看docker容器内存和CPU使用情况
  2. 高内存使用量应用,进入容器 jps查询java 进程id
  3. jstat 命令查看年轻代和老年代内存使用量和分布。
  4. jmap查看内存详情,分析问题原因为:青年代没有设置或者设置的过大了。青年代过大触发GC频率过低。造成大量垃圾对象堆积在青年代不回收。而Eden区多次扩容导致。
  5. 设置JVM参数限制内存大小:-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -Xms128m -Xmx128m -Xmn32m -Xss256k -XX:SurvivorRatio=6 -XX:+UseConcMarkSweepGC【数值可以自己设置不必照抄】
  6. docker容器运行时设置参数-e JAVA_OPTS 没有生效。
  7. 为设置JVM参数多次实验 Dockerfile中设置的参数可以生效:ENTRYPOINT "java", "-jar","-XX:MaxMetaspaceSize=256m","-Xms128m","-Xmx128m","-Xmn32m","-XX:SurvivorRatio=6","app.jar","--spring.config.location=/opt/application.yaml"
  8. 重新构建应用镜像后部署问题解决。
相关推荐
To_OC4 小时前
跑了3个Docker容器后,我终于搞懂镜像和容器到底啥关系
后端·docker·容器
东风破_4 小时前
Docker 基础:为什么需要容器?
前端·后端·nginx
葡萄成熟时 !4 小时前
JAVA 常用API学习笔记
java·笔记·学习
东风破_4 小时前
Docker 实战:使用 Nginx 作为容器入口代理 Node 服务
前端·后端·nginx
Lyyaoo.5 小时前
Spring Boot Validation 声明式参数校验
spring boot·后端·mysql
剩下了什么5 小时前
float32 与 float64 精度陷阱:如何在 Go 中避免错误使用
开发语言·后端·golang
Rain的Java大神之路5 小时前
介绍一下分布式事务
java·分布式·后端·spring·spring cloud·架构·springcloud
吴声子夜歌6 小时前
Java面试题——基础(一)
java·开发语言
南城以南溫暖如初1476 小时前
外卖CPS实战指南:从技术选型到运营落地全流程解析
java·spring boot·mysql·架构·vue
傻啦嘿哟7 小时前
英雄联盟皮肤爬虫:爬取全皮肤价格与特效,做比价工具
java·c++·爬虫