k8s 部署 springboot 项目内存持续增长问题分析解决

news2024/11/14 13:27:52

写在前面


  • 工作中遇到,请教公司前辈解决,简单整理记忆
  • 博文内容涉及一次 GC 问题的分析以及解决
  • 理解不足小伙伴帮忙指正 😃,生活加油

99%的焦虑都来自于虚度时间和没有好好做事,所以唯一的解决办法就是行动起来,认真做完事情,战胜焦虑,战胜那些心里空荡荡的时刻,而不是选择逃避。不要站在原地想象困难,行动永远是改变现状的最佳方式


遇到了什么问题

一个线上的项目,部署在 K8s 上,随着业务量的增加,内存持续增长,当内存上升到申请的内存之后,Web 系统开始卡顿,很长时间无法使用,目前没有什么好的解决办法,每天重启一次。项目中涉及到大量的对其他服务的调用,而且返回报文较大。

下面为一个业务高峰内存异常时,当前Pod 资源使用情况

接口调用返回 JSON 报文 1.79MB,耗时 41.61s

如何解决:

请教公司前辈,做内存 CG 分析,定位问题,怀疑是频繁的CG导致

这里我们先看下在本地环境如何分析,在分析之前,先简单介绍下用的到工具

Apache JMeter

Apache JMeter 是一个功能强大的开源负载测试工具,可以用于测试各种应用的性能,包括Web应用、数据库、FTP服务

支持多种协议和场景,包括HTTP、Web服务、数据库等。适合进行接口测试和压力测试

实际压测,需要添加线程组,配置报文头请求内容结果树汇总报告

适用场景:适用于Web应用、API服务、分布式系统等性能测试

JVisualVM

一个免费的、集成了多个JDK命令行工具的可视化工具。它能提供强大的分析能力,包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和CPU分析等。

下面为 CPU, 内存,类,线程的跟踪

下面为VisualVM 中 GC可视化插件,这个插件默认没有,需要单独下载安装

这里我们顺便回忆一下,JVM内存模型

+----------------------------------+
|          JVM 内存模型            |
|                                  |
|  +------------------+          |
|  |(Heap)      |<---------+|
|  |                  |          |
|  +------------------+          |
|                                  |
|  +------------------+          |
|  | 方法区/元空间     |<---------+|
|  | (Method Area/Metaspace)|       |
|  +------------------+          |
|                                  |
|  +------------------+          |
|  | 虚拟机栈(JVM Stack) |<---------+|
|  |                  |          |
|  +------------------+          |
|                                  |
|  +------------------+          |
|  | 本地方法栈(Native Method Stack) |<---------+|
|  +------------------+          |
|                                  |
|  +------------------+          |
|  | 程序计数器(Program Counter) |<---------+|
|  +------------------+          |
+----------------------------------+

和 GC 对应的关系

+------------------+      +------------------+      +------------------+
|  堆内存(Heap)    | ----> |  方法区(Method Area) | ----> |  本地方法栈(Native Method Stack) |
+------------------+      +------------------+      +------------------+
       |                        ^                     |
       |                        |                     |
       v                        |                     v
+------------------+      +------------------+      +------------------+
|  新生代(Young Generation) |      |  持久代(PermGen, Java 7及之前) |      |  线程栈(Thread Stack) |
|  (Eden, Survivor)  |      |  (已废弃, Java 8起使用元空间) |      |  (存储局部变量和方法调用) |
+------------------+      +------------------+      +------------------+
       |                            |
       |                            v
       |                     +------------------+
       |                     |  元空间(Metaspace) |
       |                     +------------------+
       |                            |
       v                            v
+------------------+      +------------------+
|  老年代(Old Generation) |      |  代码缓存(Code Cache) |
+------------------+      +------------------+

界面说明

左侧 为实时的内存使用 元数据区(Metaspace)老年待(Old)新生代(Eden区 + Survivor区(S0和S1))

右侧 为 日志相关,涉及编译,类加载以及GC的日志

编译时间:11569次编译,每次编译耗时2.620秒。
类加载时间:15689个类被加载,平均每次加载耗时10.402秒。
GC 时间:197次GC,共27615毫秒。

GC日志

Eden新生代总大小为1.310G,使用量为710.500M,共182次收集,每次收集耗时17.848秒。
幸存区0和1的GC日志:

  • Surviver 0(447.500M, 301.500M):使用量为11.828M,显示了每次GC的时间分布和使用情况, 447.500M是幸存区0的上限,而301.500M是幸存区0的下限。
  • Surviver 1(447.500M, 316.000M):使用量为0.0M,显示了每次GC的时间分布和使用情况。

Old Gen:显示了老年代的内存使用情况,总大小为2.623G,使用量为663.208M,共15次收集,每次收集耗时9.767秒。

Metaspace:显示了元空间的内存使用情况,总大小为1.072G,使用量为86.086M。

这里我们简单回顾一下 分代垃圾回收机制

Heap 数据最先到达 eden 区,当Eden区满时,会触发Minor GC(也称为Young GC),将存活的对象转移到Survivor区(S0和S1)。Eden区的设计目标是快速分配内存,因此它通常比Survivor区大

Survivor 区有两个区域:S0和S1(有时也称为From和To)。在Minor GC过程中,存活的对象会从Eden区复制到Survivor区,并在两个Survivor区之间来回复制,直到它们达到一定年龄(由JVM参数MaxTenuringThreshold决定),然后晋升到老年代(Old Generation)Survivor区的设计目标是提供足够的空间以支持频繁的Minor GC,同时避免内存碎片。

经过多次Minor GC后,仍然存活的对象会被晋升到老年代。晋升到老年代的条件包括对象的大小、年龄以及Survivor区的空间使用情况。

当老年代空间不足时,会触发Major GC(也称为Old GC),回收老年代中不再使用的对象

major gc 什么时候会发生,它和 full gc 的区别是什么?

major gc很多参考资料指的是等价于full gc,即老年代的GC,我们也可以发现很多性能监测工具中只有minor gcfull gc

一般情况下,一次full gc将会对年轻代、老年代以及元空间、堆外内存进行垃圾回收。而触发Full GC的原因有很多:

  • 当年轻代晋升到老年代的对象大小比目前老年代剩余的空间大小还要大时,此时会触发Full GC;
  • 当老年代的空间使用率超过某阈值时,此时会触发Full GC;
  • 当元空间不足时(JDK1.7永久代不足),也会触发Full GC;
  • 当调用System.gc()也会安排一次Full GC;

问题复现

选择一个合适的接口 通过 JMeter 做压力测试

先配置较小的 堆分配,添加 JVM 参数 -Xms2024m -Xmx2024m, 通过 JVisualVM GC 查看内存日志

可以看到 即使GC 频繁回收,老年代和新生代的内存都是 100%,这个时候系统就基本属于卡顿状态

项目报错提示

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1087)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:665)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)

在处理Spring框架的Web请求时,发生了嵌套的Servlet异常。根本原因是Java堆内存不足(OutOfMemoryError: Java heap space)

所以这里我们 增加Java堆内存的大小,修改 启动参数 -Xms4024m -Xmx4024m

可以看到即使配置了 4G 的内存,还是存在问题,GC 频繁回收,老年代和新生代的内存使用没有下去。

项目抱错提示

org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1087)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:665)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)

Java垃圾回收器(GC)花费的时间过长,超过了默认的阈值(98%),导致应用程序无法正常运行。

在实际的场景中,如果是在 云平台,没有对应的桌面工具使用,可以考虑使用 Java 自带的一些性能分析工具

bash-4.4# which java
/opt/jdk/bin/java
bash-4.4# cd /opt/jdk/bin/
bash-4.4# ls
ControlPanel    jar             javac           javap           jconsole        jhat            jmc             jsadebugd       jstatd          orbd            rmid            servertool      wsimport
appletviewer    jarsigner       javadoc         javapackager    jcontrol        jinfo           jmc.ini         jstack          jvisualvm       pack200         rmiregistry     tnameserv       xjc
extcheck        java            javafxpackager  javaws          jdb             jjs             jps             jstack.log      keytool         policytool      schemagen       unpack200
idlj            java-rmi.cgi    javah           jcmd            jdeps           jmap            jrunscript      jstat           native2ascii    rmic            serialver       wsgen
bash-4.4# 

JDK bin 目录下有一些 Java 常用的性能分析工具

这里命令最后面的 1 为进程 id,容器化部署,所以主进程即为 业务进程,如果不是容器化部署,可能需要 top, pgrep 等命令来确定 进程ID

使用jinfo命令查看JVM的配置参数的输出

bash-4.4# jinfo -flags 1
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.192-b12
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=4781506560 -XX:MaxHeapSize=4781506560 -XX:MaxNewSize=1593835520 -XX:MinHeapDeltaBytes=196608 -XX:NewSize=1593835520 -XX:OldSize=3187671040 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps 
Command line:  -Xms4560m -Xmx4560m
bash-4.4#
  • JVM版本:JVM版本是25.192-b12。
  • 非默认VM标志:这些是JVM的非默认配置参数,包括:
  • XX:CICompilerCount=2:设置编译器数量为2。
  • XX:InitialHeapSize=4781506560:设置初始堆大小为4560.0MB。
  • XX:MaxHeapSize=4781506560:设置最大堆大小为4560.

使用 jmap 命令查看 JVM堆内存 配置和使用的输出。

下面为负载正常的输出

bash-4.4# jmap -heap 1
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.192-b12

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4781506560 (4560.0MB)
   NewSize                  = 1593835520 (1520.0MB)
   MaxNewSize               = 1593835520 (1520.0MB)
   OldSize                  = 3187671040 (3040.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 1434451968 (1368.0MB)
   used     = 644721408 (614.854248046875MB)
   free     = 789730560 (753.145751953125MB)
   44.945485968338815% used
Eden Space:
   capacity = 1275068416 (1216.0MB)
   used     = 615064856 (586.5715560913086MB)
   free     = 660003560 (629.4284439086914MB)
   48.237792441719456% used
From Space:
   capacity = 159383552 (152.0MB)
   used     = 29656552 (28.282691955566406MB)
   free     = 129727000 (123.7173080444336MB)
   18.607034181293688% used
To Space:
   capacity = 159383552 (152.0MB)
   used     = 0 (0.0MB)
   free     = 159383552 (152.0MB)
   0.0% used
tenured generation:
   capacity = 3187671040 (3040.0MB)
   used     = 42750216 (40.76978302001953MB)
   free     = 3144920824 (2999.2302169799805MB)
   1.3411112835532741% used

40672 interned Strings occupying 4176272 bytes.
bash-4.4# 

  • JVM版本:JVM版本是25.192-b12。
  • GC类型:使用的是Mark Sweep Compact GC(标记-清除-紧凑)类型的垃圾回收器。
  • 堆内存配置:
    • 最小堆空闲比率(MinHeapFreeRatio):40%
    • 最大堆空闲比率(MaxHeapFreeRatio):70%
    • 最大堆大小(MaxHeapSize):4560.0MB
    • 新生代大小(NewSize):1520.0MB
    • 最大新生代大小(MaxNewSize):1520.0MB
    • 老年代大小(OldSize):3040.0MB
    • 新生代和老年代的比例(NewRatio):2
    • Survivor区的比例(SurvivorRatio):8
    • 元空间大小(MetaspaceSize):20.796875MB
    • 压缩类空间大小(CompressedClassSpaceSize):1024.0MB
    • 最大元空间大小(MaxMetaspaceSize):17592186044415 MB
    • G1堆区域大小(G1HeapRegionSize):0.0MB
  • 堆内存使用情况: 配额:capacity ,used 使用的,free 空闲的
    • 新生代(Eden + 1 Survivor Space):总容量为1368.0MB…
    • 老年代的使用率非常低 1.3411112835…

下面为负载异常的输出:

bash-4.4# jmap -heap 1
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.192-b12

using thread-local object allocation.
Mark Sweep Compact GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4781506560 (4560.0MB)
   NewSize                  = 1593835520 (1520.0MB)
   MaxNewSize               = 1593835520 (1520.0MB)
   OldSize                  = 3187671040 (3040.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 1434451968 (1368.0MB)
   used     = 1366285424 (1302.9913177490234MB)
   free     = 68166544 (65.00868225097656MB)
   95.24790334422686% used
Eden Space:
   capacity = 1275068416 (1216.0MB)
   used     = 1275068416 (1216.0MB)
   free     = 0 (0.0MB)
   100.0% used
From Space:
   capacity = 159383552 (152.0MB)
   used     = 91217008 (86.99131774902344MB)
   free     = 68166544 (65.00868225097656MB)
   57.23113009804174% used
To Space:
   capacity = 159383552 (152.0MB)
   used     = 0 (0.0MB)
   free     = 159383552 (152.0MB)
   0.0% used
tenured generation:
   capacity = 3187671040 (3040.0MB)
   used     = 3187671024 (3039.999984741211MB)
   free     = 16 (1.52587890625E-5MB)
   99.99999949806615% used

36548 interned Strings occupying 3847648 bytes.
bash-4.4# 

两个很明显的表现

  • 老年代(tenured generation:)的使用率非常高,几乎达到了100%(99.99999949806615%),这表明老年代中的对象已经填满了。
  • 新生代(New Generation (Eden + 1 Survivor Space))的使用率也比较高,95.24790334422686% used

这个时候我们可以通过 watch 命令来持续监听命令的输出

bash-4.4# watch jmap -heap 1

问题分析

没有太多的内存供程序使用,我们需要从下面几个方向入手:

  • 找到 GC 频繁的原因,即内存爆炸增长的地方,想办法减少大对象的创建
  • 考虑 调整垃圾回收器的阈值
  • GC 优化,考虑选择合适的垃圾回收器

第一个原因需要从代码角度解决,在代码层面,减少内存使用,两个方法考虑:

  • 一个是纵向处理,即在对象本身上考虑,对大对象瘦身,减少不必要的组合对象(考虑建造者设计模式),或者本身。
  • 一个是横向处理,即在对象数量上考虑,减少对象创建的数量,比如考虑单例,原型 等设计模式。

第二个调整阈值,可以确定当前和阈值关系不大,修改 GC 频率太大会影响 正常线程的执行,当JVM 进行GC 是,会对当前执行的线程有一定的影响,具体和 JDK 版本 垃圾回收器都有一定的关系。

垃圾回收器进行GC时会暂停应用程序的所有线程(Stop-The-World),以便能够安全地回收不再使用的对象。这种暂停时间的长短取决于垃圾回收器的类型和堆内存的大小

以下是GC对线程的一些影响:

  1. 暂停时间:当GC进行时,所有线程都会暂停执行。这意味着应用程序的用户界面可能会冻结,或者长时间运行的任务可能会延迟完成。
  2. 线程优先级:在GC期间,JVM可能会调整线程的优先级,以确保垃圾回收器能够顺利运行。这可能会导致某些线程在GC期间执行的频率降低。
  3. 线程间通信:由于所有线程都被暂停,因此在GC期间线程间的通信可能会受到影响。这可能会导致某些同步操作失败或数据不一致。
  4. 资源利用:GC过程中,JVM会占用一部分CPU和内存资源来执行垃圾回收任务。这可能会导致应用程序的性能下降。

minor gc(新生代的垃圾回收) 是否会导致 stop the world ?

不管什么GC,都会发送stop the world,区别是发生的时间长短。而这个时间跟垃圾收集器又有关系,Serial、PartNew、Parallel Scavenge收集器无论是串行还是并行,都会挂起用户线程,而CMS和G1在并发标记时,是不会挂起用户线程,但其他时候一样会挂起用户线程,stop the world的时间相对来说小很多了。

问题解决

这里我们简单回顾下 JVM 垃圾回收算法类型及其优缺点,以及回收器

标记-清除算法(Mark-Sweep):标记直接清除

  • 优点:不需要移动对象,简单高效。
  • 缺点:标记-清除过程效率低,GC产生内存碎片。

复制算法(Copying):整体复制,需要额外的空间

  • 优点:简单高效,不会产生内存碎片。
  • 缺点:内存使用率低,且有可能产生频繁复制问题。

标记-整理算法(Mark-Compact):先标记,然后需要清理的和不需要清理的分组

  • 优点:综合了前两种算法的优点。
  • 缺点:仍需要移动局部对象。

分代收集算法(Generational Collection)

  • 优点:分区回收
  • 缺点: 对于长时间存活对象的场景回收效果不明显,甚至起到反作用。

常见回收器分类

回收器类型回收算法特点设置参数
Serial New/ Serial Old回收器复制算法/标记-整理算法单线程复制回收,简单高效,但会暂停程序导致停顿-XX:+UseSerialGC(年轻代、老年代回收器为:Serial New、Serial Old)
ParNew New/ ParNew Old回收器复制算法/标记-整理算法多线程复制回收,降低了停顿时间,但容易增加上下文切换-XX:+UseParNewGC(年轻代、老年代回收器为:ParNew New、Serial Old,JDK1.8中无效)
- XX:+UseParallelOldGC(年轻代、老年代回收器为:Parallel Scavenge、Parallel Old)
Parallel Scavenge回收器复制算法并行回收器,追求高吞吐量,高效利用CPU-XX:+UseParallelGC(年轻代、老年代回收器为:Parallel Scavenge、Serial Old)
- XX:ParallelGCThreads=4(设置并发线程)
CMS回收器标记-清理算法老年代回收器,高并发、低停顿,追求最短GC回收停顿时间,CPU占用比较高,响应时间快,停顿时间短-XX:+UseConcMarkSweepGC(年轻代、老年代回收器为:ParNew New、CMS(Serial Old作为备用))
G1回收器标记-整理+复制算法高并发、低停顿,可预测停顿时间-XX:+UseG1GC(年轻代、老年代回收器为:G1、G1)
- XX:MaxGCPauseMillis=200(设置最大暂停时间)
GC 优化

传统的 GC 优化一般为:

  • 减少 新生代 GC(Minor GC) 次数
  • 减少 老年代 GC(Full GC) 次数
  • 选择合适的垃圾回收器

前两种在实际的优化中需要不断的调整 新生代和老年代的堆内存配额,结合业务负载选择合适的阈值,稍微比较麻烦。

所以我们先从选择合适的垃圾回收器开始,当前使用的 Jdk1.8,通过上面的 heap 信息输出可以看到默认情况下使用的垃圾回收器为 Mark Sweep Compact GC,即我们经常讲的 CMS

CMS在 1.9 被标记为废弃,主要原因在于标记清除下的悬浮内存,导致内存空间碎片化,进而导致full GC的发生。full GC 往往消耗更多的时间。

考虑使用 1.9 后主推的G1,所以这里我们使用 G1 尝试

G1CMS的优势在于以下几点:

  1. 并行与并发:G1能够更充分利用多CPU、多核环境运行
  2. 分代收集:G1虽然也用了分代概念,但相比其他收集器需要配合不同收集协同工作,但G1收集器能够独立管理整个堆
  3. 空间管理:与CMS的标记一清理算法不同,G1从整体上基于标记一整理算法,将整个Java堆划分为多个大小相等的独立区域(Region),这种算法能够在运行过程中不产生内存碎片
  4. 可预测的停顿:降低停顿时间是G1和CMS共同目标,但是G1追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒。

修改启动参数尝试 -Xms4024m -Xmx4024m -XX:+UseG1GC

做压力测试,可以看到 修改了 G1 回收器后,相同的压测线程数,JVisualVM 数据展示,老年代可以正常回收,即使频繁发生 full GC

可以看到相关对 上面的 CMS ,G1 在数据展示上多了最下面的 Histogram 区域:

Histogram: 对象年龄分布的直方图,显示了对象在不同年龄阶段的数量。

Parameters: 配置参数,包括:

  • Tenuring Threshold: 晋升阈值,当前值为1。
  • Max Tenuring Threshold: 最大晋升阈值,当前值为15。
  • Desired Survivor Size: 期望的幸存区大小,当前值为25165824。
  • Current Survivor Size: 当前幸存区大小,当前值为8。

我们在容器环境通过 jmap 查看 GC信息

bash-4.4# jmap -heap 1
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.192-b12

using thread-local object allocation.
Garbage-First (G1) GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4223664128 (4028.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 2533359616 (2416.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 1048576 (1.0MB)

Heap Usage:
G1 Heap:
   regions  = 4028
   capacity = 4223664128 (4028.0MB)
   used     = 396765408 (378.3849792480469MB)
   free     = 3826898720 (3649.615020751953MB)
   9.39386740933582% used
G1 Young Generation:
Eden Space:
   regions  = 225
   capacity = 2652897280 (2530.0MB)
   used     = 235929600 (225.0MB)
   free     = 2416967680 (2305.0MB)
   8.893280632411066% used
Survivor Space:
   regions  = 7
   capacity = 7340032 (7.0MB)
   used     = 7340032 (7.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 148
   capacity = 1563426816 (1491.0MB)
   used     = 153495776 (146.38497924804688MB)
   free     = 1409931040 (1344.6150207519531MB)
   9.81790605285358% used

62301 interned Strings occupying 6150008 bytes.
bash-4.4# 

Garbage-First (G1) GC with 8 thread(s) 当前使用的垃圾回收算法,可以看到 GC 相关数据趋于平稳

代码层面优化

对应上面的第一个方向,即大内存对象的处理上,对下面的一些做了修改

横向处理:

  • 静态方法内调用 RestTemplate 实例发送请求,由每次 new 改成了单例
RestTemplate restTemplate = new RestTemplate();

修改为:

// 创建请求实体
RestTemplate restTemplate = SingletonFactoryRestTemple.getInstance();
=============
/**
 *  RestTemplate 单例
 */
public class SingletonFactoryRestTemple {
    private static RestTemplate instance;
    // 私有构造函数
    private SingletonFactoryRestTemple() {
        // 防止通过反射创建多个实例
        if (instance != null) {
            throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
        }
    }
    // 静态公共方法,用于获取实例
    public static RestTemplate getInstance() {
        if (instance == null) {
            synchronized (SingletonFactoryRestTemple.class) {
                if (instance == null) {
                    instance = new RestTemplate();
                }
            }
        }
        return instance;
    }
}
  • 在for循环内部存在每次创建对象解析模板创建改成了循环外创建一次,循环内重复使用

纵向处理

通过 jmap 对 head 内的对象内存使用直方图输出,可以看到用的最多的为 char[]String

^Cbash-4.4# jmap -histo:live 1 | head -20

 num     #instances         #bytes  class name
----------------------------------------------
   1:      23497860     1344485416  [C
   2:      23566026      565584624  java.lang.String
   3:       3274822      235787184  com.sun.xml.internal.messaging.saaj.soap.impl.ElementImpl
   4:       4080048      195842304  com.sun.org.apache.xerces.internal.dom.AttrNSImpl
   5:         13983      144511032  [I
   6:       2226796       90912544  [Ljava.lang.Object;
   7:       3283152       78795648  javax.xml.namespace.QName
   8:       3274976       78599424  com.sun.xml.internal.messaging.saaj.soap.impl.ElementImpl$AttributeManager
   9:       1776891       71075640  com.sun.xml.internal.messaging.saaj.soap.impl.TextImpl
  10:          9628       68625592  [B
  11:       2376607       57038568  com.sun.org.apache.xerces.internal.dom.AttributeMap
  12:       2213528       53124672  java.util.ArrayList
  13:         64656        5689728  java.lang.reflect.Method
  14:        135717        5428680  java.util.LinkedHashMap$Entry
  15:        194489        4667736  com.ruoyi.hotel.service.UserService.ArrayOfKeyValueOfanyTypeanyType.KeyValueOfanyTypeanyType
  16:        144083        4610656  java.util.concurrent.ConcurrentHashMap$Node
  17:        125953        4030496  java.util.HashMap$Node
bash-4.4# 
  • 所以对 String 类型的大的 HTTP 报文做了瘦身处理,对XML 复杂报文做了标签替换。

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 😃



© 2018-2024 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1904544.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ES7210高性能四通道音频ADC转换模拟麦克风为IIS数字咪头

特征 高性能多位 Delta-Σ 音频 ADC 102 dB 信噪比 -85 分贝 THDN 24 位&#xff0c;8 至 100 kHz 采样频率 I2S/PCM 主串行数据端口或从串行数据端口 支持TDM 256/384Fs、USB 12/24 MHz 和其他非标准音频系统时钟 低功耗待机模式 应用 麦克风阵列 智能音箱 远场语音捕获 订购…

npm安装完yarn还是用不了?

前言 解决 找到你的包全局安装目录 复制路径&#xff0c;配置到Path全局环境变量 结果 不过发现在idea里还是用不了&#xff0c;此时你会想&#xff0c;这什么烂贴&#xff0c;没一点屁用 不过在重启idea之后&#xff0c;你也许就不会这么想了

【网络安全】实验五(身份隐藏与ARP欺骗)

一、本次实验的实验目的 &#xff08;1&#xff09;了解网络攻击中常用的身份隐藏技术&#xff0c;掌握代理服务器的配置及使用方法 &#xff08;2&#xff09;通过实现ARP欺骗攻击&#xff0c;了解黑客利用协议缺陷进行网络攻击的一般方法 二、搭配环境 打开三台虚拟机&#…

本地多卡(3090)部署通义千问Qwen2-72B大模型提速实践:从龟速到够用

最近在做文本风格转化&#xff0c;涉及千万token级别的文本。想用大模型转写&#xff0c;在线的模型一来涉及数据隐私&#xff0c;二来又不想先垫钱再找报销。本地的7-9B小模型又感觉效果有限&#xff0c;正好实验室给俺配了4卡3090的机子&#xff0c;反正也就是做个推理&#…

掌握MySQL基础命令:数据表结构修改详细操作

MySQL数据表&#xff08;Table&#xff09;是MySQL数据库中存储数据的基本结构单元。简单来说&#xff0c;数据表可以被看作是一个二维的、由行&#xff08;Row&#xff09;和列&#xff08;Column&#xff09;组成的表格&#xff0c;其中每一行代表了一个记录&#xff08;Reco…

微服务的分布式事务解决方案

微服务的分布式事务解决方案 1、分布式事务的理论模型1.1、X/Open 分布式事务模型1.2、两阶段提交协议1.3、三阶段提交协议 2、分布式事务常见解决方案2.1、TCC补偿型方案2.2、基于可靠性消息的最终一致性方案2.3、最大努力通知型方案 3、分布式事务中间件 Seata3.1、AT 模式3.…

数据跨境法案:美国篇上

近年来随着全球数字化的加速发展&#xff0c;数据已成为国家竞争力的重要基石。在这样的背景下&#xff0c;中国软件和技术出海的场景日益丰富。本系列邀请到在跨境数据方面的研究人员针对海外的数据跨境政策进行解读。 本期将针对美国对数据跨境流动的态度和政策进行阐释。过…

基础权限存储

一丶要求 建立用户组shengcan&#xff0c;其id为 2000建立用户组 caiwu&#xff0c;其id 为2001建立用户组 jishu&#xff0c;其id 为 2002建立目录/sc,此目录是 shengchan 部门的存储目录&#xff0c;只能被 shengchan 组的成员操作4.其他用户没有任何权限建立目录/cw,此目录…

两个全开源的3D模型素材下载网站源码 3D图纸模型素材 三维图形素材会员下载站源码

今天推荐两个全开源的3D模型素材下载网站源码 3D图纸模型素材 三维图形素材会员下载站源码&#xff0c;这两个源码完整&#xff0c;都是基于thinkphp内核开发的&#xff0c;框架稳定&#xff0c;带数据库&#xff0c;源码文件&#xff0c;可以直接部署使用。 第一个&#xff1a…

数据库课设---学生宿舍管理系统(sql server+C#)

1.引言 1.1 内容及要求 设计内容&#xff1a;设计学生宿舍管理系统。 设计要求&#xff1a; &#xff08;1&#xff09;数据库应用系统开发的需求分析&#xff0c;写出比较完善系统功能。 &#xff08;2&#xff09;数据库概念模型设计、逻辑模型设计以及物理模型设计。 …

【基于R语言群体遗传学】-10-适应性与正选择

在之前的博客中&#xff0c;我们学习了哈代温伯格模型&#xff0c;学习了Fisher模型&#xff0c;学习了遗传漂变与变异的模型&#xff0c;没有看过之前内容的朋友可以先看一下之前的文章&#xff1a; 群体遗传学_tRNA做科研的博客-CSDN博客 一些新名词 &#xff08;1&#xf…

AI绘画Stable Diffusion【图生图教程】:图片高清修复的三种方案详解,你一定能用上!(附资料)

大家好&#xff0c;我是画画的小强 今天给大家分享一下用AI绘画Stable Diffusion 进行 高清修复&#xff08;Hi-Res Fix&#xff09;&#xff0c;这是用于提升图像分辨率和细节的技术。在生成图像时&#xff0c;初始的低分辨率图像会通过放大算法和细节增强技术被转换为高分辨…

隔离级别-隔离级别中的锁协议、隔离级别类型、隔离级别的设置、隔离级别应用

一、引言 1、DBMS除了采用严格的两阶段封锁协议来保证并发事务的可串行化&#xff0c;实现事务的隔离性&#xff0c;也可允许用户选择一个可以保证应用程序正确执行并且能够使并发度最大的隔离性等级 2、通常用隔离级别来描述隔离性等级&#xff0c;以下将主要介绍ANSI 92标准…

【数据结构】链表带环问题分析及顺序表链表对比分析

【C语言】链表带环问题分析及顺序表链表对比分析 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C语言学习之路 文章目录 【C语言】链表带环问题分析及顺序表链表对比分析前言一.顺序表和链表对比1.1顺序表和链表的区别1.2缓存利用率&#…

Blender新手入门笔记收容所(一)

基础篇 基础操作 视角的控制 控制观察视角&#xff1a;鼠标中键平移视图&#xff1a;Shift鼠标中键缩放视图&#xff1a;滚动鼠标中键滚轮 选中物体后&#xff1a;移动物体快捷键G&#xff0c;移动后单击鼠标就会定下来。 进入移动状态后&#xff1a;按Y会沿着Y轴移动进入移动…

谷粒商城学习笔记-17-快速开发-逆向工程搭建使用

文章目录 一&#xff0c;克隆人人开源的逆向工程代码二&#xff0c;把逆向工程集成到谷粒商城的后台工程三&#xff0c;以商品服务为例&#xff0c;使用逆向工程生成代码1&#xff0c;修改逆向工程的配置2&#xff0c;以Debug模式启动逆向工程3&#xff0c;使用逆向工程生成代码…

机器学习Day12:特征选择与稀疏学习

1.子集搜索与评价 相关特征&#xff1a;对当前学习任务有用的特征 无关特征&#xff1a;对当前学习任务没用的特征 特征选择&#xff1a;从给定的特征集合中选择出相关特征子集的过程 为什么要特征选择&#xff1f; 1.任务中经常碰到维数灾难 2.去除不相关的特征能降低学习的…

ASCII码对照表(Matplotlib颜色对照表)

文章目录 1、简介1.1 颜色代码 2、Matplotlib库简介2.1 简介2.2 安装2.3 后端2.4 入门例子 3、Matplotlib库颜色3.1 概述3.2 颜色图的分类3.3 颜色格式表示3.4 内置颜色映射3.5 xkcd 颜色映射3.6 颜色命名表 4、Colorcet库5、颜色对照表结语 1、简介 1.1 颜色代码 颜色代码是…

Koa2实现多并发文件上传

koa2批量上传文件 目前的是为了实现批量导入md文件&#xff0c;发布文章。这样就不用自己一篇一篇同步文章了。一次可以同步几千篇文章。 实现界面 内容 主要包含上传的文件标题&#xff0c;文件大小&#xff0c;上传状态。 <el-upload ref"uploader" v-model:…

维护el-table列,循环生成el-table

1、lib/setting.js&#xff08;维护table列&#xff09; const columns[{ label: 类型, prop: energyName, width: 150, isText: true },{ label: 消耗量(t或10⁴m), prop: inputNum, isInput: true },{label: CO₂,children: [// { label: 核算因子, prop: co2FactorValue, w…