寫在前面
- 工作中遇到,請教公司前輩解決,簡單整理記憶
- 博文內容涉及一次 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 gc
和full 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 spaceat 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 exceededat 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-b12using thread-local object allocation.
Mark Sweep Compact GCHeap Configuration:MinHeapFreeRatio = 40MaxHeapFreeRatio = 70MaxHeapSize = 4781506560 (4560.0MB)NewSize = 1593835520 (1520.0MB)MaxNewSize = 1593835520 (1520.0MB)OldSize = 3187671040 (3040.0MB)NewRatio = 2SurvivorRatio = 8MetaspaceSize = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize = 17592186044415 MBG1HeapRegionSize = 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% used40672 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-b12using thread-local object allocation.
Mark Sweep Compact GCHeap Configuration:MinHeapFreeRatio = 40MaxHeapFreeRatio = 70MaxHeapSize = 4781506560 (4560.0MB)NewSize = 1593835520 (1520.0MB)MaxNewSize = 1593835520 (1520.0MB)OldSize = 3187671040 (3040.0MB)NewRatio = 2SurvivorRatio = 8MetaspaceSize = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize = 17592186044415 MBG1HeapRegionSize = 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% used36548 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對線程的一些影響:
- 暫停時間:當GC進行時,所有線程都會暫停執行。這意味著應用程序的用戶界面可能會凍結,或者長時間運行的任務可能會延遲完成。
- 線程優先級:在GC期間,JVM可能會調整線程的優先級,以確保垃圾回收器能夠順利運行。這可能會導致某些線程在GC期間執行的頻率降低。
- 線程間通信:由于所有線程都被暫停,因此在GC期間線程間的通信可能會受到影響。這可能會導致某些同步操作失敗或數據不一致。
- 資源利用: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
嘗試
G1
與CMS
的優勢在于以下幾點:
并行與并發
:G1能夠更充分利用多CPU、多核環境運行分代收集
:G1雖然也用了分代概念,但相比其他收集器需要配合不同收集協同工作,但G1收集器能夠獨立管理整個堆空間管理
:與CMS的標記一清理算法不同,G1從整體上基于標記一整理算法
,將整個Java堆劃分為多個大小相等的獨立區域(Region),這種算法能夠在運行過程中不產生內存碎片可預測的停頓
:降低停頓時間是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-b12using thread-local object allocation.
Garbage-First (G1) GC with 8 thread(s)Heap Configuration:MinHeapFreeRatio = 40MaxHeapFreeRatio = 70MaxHeapSize = 4223664128 (4028.0MB)NewSize = 1363144 (1.2999954223632812MB)MaxNewSize = 2533359616 (2416.0MB)OldSize = 5452592 (5.1999969482421875MB)NewRatio = 2SurvivorRatio = 8MetaspaceSize = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize = 17592186044415 MBG1HeapRegionSize = 1048576 (1.0MB)Heap Usage:
G1 Heap:regions = 4028capacity = 4223664128 (4028.0MB)used = 396765408 (378.3849792480469MB)free = 3826898720 (3649.615020751953MB)9.39386740933582% used
G1 Young Generation:
Eden Space:regions = 225capacity = 2652897280 (2530.0MB)used = 235929600 (225.0MB)free = 2416967680 (2305.0MB)8.893280632411066% used
Survivor Space:regions = 7capacity = 7340032 (7.0MB)used = 7340032 (7.0MB)free = 0 (0.0MB)100.0% used
G1 Old Generation:regions = 148capacity = 1563426816 (1491.0MB)used = 153495776 (146.38497924804688MB)free = 1409931040 (1344.6150207519531MB)9.81790605285358% used62301 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 -20num #instances #bytes class name
----------------------------------------------1: 23497860 1344485416 [C2: 23566026 565584624 java.lang.String3: 3274822 235787184 com.sun.xml.internal.messaging.saaj.soap.impl.ElementImpl4: 4080048 195842304 com.sun.org.apache.xerces.internal.dom.AttrNSImpl5: 13983 144511032 [I6: 2226796 90912544 [Ljava.lang.Object;7: 3283152 78795648 javax.xml.namespace.QName8: 3274976 78599424 com.sun.xml.internal.messaging.saaj.soap.impl.ElementImpl$AttributeManager9: 1776891 71075640 com.sun.xml.internal.messaging.saaj.soap.impl.TextImpl10: 9628 68625592 [B11: 2376607 57038568 com.sun.org.apache.xerces.internal.dom.AttributeMap12: 2213528 53124672 java.util.ArrayList13: 64656 5689728 java.lang.reflect.Method14: 135717 5428680 java.util.LinkedHashMap$Entry15: 194489 4667736 com.ruoyi.hotel.service.UserService.ArrayOfKeyValueOfanyTypeanyType.KeyValueOfanyTypeanyType16: 144083 4610656 java.util.concurrent.ConcurrentHashMap$Node17: 125953 4030496 java.util.HashMap$Node
bash-4.4#
- 所以對 String 類型的大的 HTTP 報文做了瘦身處理,對XML 復雜報文做了標簽替換。
博文部分內容參考
? 文中涉及參考鏈接內容版權歸原作者所有,如有侵權請告知 😃
…
? 2018-2024 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)