最近咱們項目升級到了 Spring Boot 2.4.6 + Spring Cloud 2020.0.x,經過個人另外一系列便可看出:Spring Cloud 升級之路。可是升級後,咱們發現 YoungGC 明顯增高,分配對象速率明顯增高,可是晉升的對象並無增多,證實都是新建立的對象而且沒過多久就能夠被回收。咱們來看其中一個進程的監控,這時候的 http 請求速率大概在 100 左右:java
這就很奇怪了,請求速率並無那麼大,可是經過監控能夠看出每秒鐘分配了將近兩個 G 的內存。在升級以前,這個分配速率大概在 100~200 MB 左右,在同等請求速率下。那麼這多出來的內存到底是哪裏消耗的呢?git
咱們須要看一下內存中各類對象的統計數據,即便用 jmap 命令。同時不能只查看存活對象的統計,由於從監控中看出來並非老年代對象過多,由於晉升的對象並無增多,相反的,咱們若是咱們能排除如今還存活的對象就更好了。同時,因爲 GC 至關頻繁,1s 左右就會有一次。因此基本不能指望一次就能抓到咱們想要的 jmap。同時 jmap 會致使全部線程進入 safepoint 從而 STW,對線上有必定影響,因此不能太頻繁 jmap。因此,咱們採起以下策略:github
jmap -histo
(統計全部對象) 以及 jmap -histo:live
(僅統計存活對象);經過這幾回的 jmap 對比,咱們發現 jmap 統計中排在前面的對象類型有一個 spring 框架的:spring
num #instances #bytes class name (module) ------------------------------------------------------- 1: 7993252 601860528 [B (java.base@11.0.8) 2: 360025 296261160 [C (java.base@11.0.8) 3: 10338806 246557984 [Ljava.lang.Object; (java.base@11.0.8) 4: 6314471 151547304 java.lang.String (java.base@11.0.8) 5: 48170 135607088 [J (java.base@11.0.8) 6: 314420 126487344 [I (java.base@11.0.8) 7: 4591109 110100264 [Ljava.lang.Class; (java.base@11.0.8) 8: 245542 55001408 org.springframework.core.ResolvableType 9: 205234 29042280 [Ljava.util.HashMap$Node; (java.base@11.0.8) 10: 386252 24720128 [org.springframework.core.ResolvableType; 11: 699929 22397728 java.sql.Timestamp (java.sql@11.0.8) 12: 89150 21281256 [Ljava.beans.PropertyDescriptor; (java.desktop@11.0.8) 13: 519029 16608928 java.util.HashMap$Node (java.base@11.0.8) 14: 598728 14369472 java.util.ArrayList (java.base@11.0.8)
這個對象是怎麼建立出來的呢?如何定位一個已經再也不存活的頻繁建立對象,而且這個對象類型是框架內部的?sql
首先,MAT(Eclipse Memory Analyzer)+ jmap dump
這種整個堆分析,並不太適用,緣由是:編程
雖然這個問題不能這麼定位,我仍是將我採集的 jmap dump 結果放在這裏用 MAT 分析的結果展現出來給你們看下:緩存
那麼接下來怎麼分析呢?這就又用到了咱們的老朋友,JFR + JMC。老讀者知道,我常用 JFR 定位線上問題,這裏怎麼使用呢?並無直接的 JFR 事件統計常常建立哪些對象,可是呢,有間接的事件,能夠間接體現是誰建立了這麼多對象。我通常這麼定位:微信
首先查看 Thread Allocation Statistics 事件,發現基本上全部 servlet 線程(就是處理 Http 請求的線程,咱們用的 Undertow,因此線程名稱是 XNIO 開頭的),分配的對象都不少,這樣並不能定位問題:框架
而後咱們來看熱點代碼統計,點擊 Method Profiling Sample 事件,查看堆棧追蹤統計,看哪些佔比比較高。單元測試
發現佔比靠前的,貌似都和這個 ResolvableType
有關,進一步定位,雙擊第一個方法查看調用堆棧統計:
咱們發現,調用它的是 BeanUtils.copyProperties
。查看其它ResolvableType
有關的調用,都和BeanUtils.copyProperties
有關。這個方法是咱們項目中常用的方法,用於同類型或者不一樣類型之間的屬性複製。這個方法爲什麼會建立這麼多 ResolvableType
呢?
經過查看源碼,咱們發現從 Spring 5.3.x 開始,BeanUtils
開始經過建立 ResolvableType
這個統一類信息封裝,進行屬性複製:
/** * * <p>As of Spring Framework 5.3, this method honors generic type information */ private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException { }
裏面的源碼,每次都針對源對象和目標對象的類型的每一個屬性方法建立了新的 ResolvableType
,而且沒有作緩存。這致使一次複製,會建立出來大量的 ResolvableType
.咱們來作個試驗:
public class Test { public static void main(String[] args) { TestBean testBean1 = new TestBean("1", "2", "3", "4", "5", "6", "7", "8", "1", "2", "3", "4", "5", "6", "7", "8"); TestBean testBean2 = new TestBean(); for (int i = 0; i > -1; i++) { BeanUtils.copyProperties(testBean1, testBean2); System.out.println(i); } } }
分別使用 spring-beans 5.2.16.RELEASE
和 spring-beans 5.3.9
這兩個依賴去執行這個代碼,JVM 參數使用 -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx512m
.這些參數的意思是,使用 EpsilonGC,也就是在堆內存滿的時候,不執行 GC,直接拋出 OutofMemory 異常並程序結束,而且最大堆內存是 512m。這樣,程序其實就是看:在內存耗盡以前,不一樣版本的 BeanUtils.copyProperties
分別能執行多少次。
試驗結果是:spring-beans 5.2.16.RELEASE
是 444489 次,spring-beans 5.3.9
是 27456 次。這是至關大的差距啊。
因而,針對這個問題,我向 spring-framework github 提了個 Issue.
而後,對於項目中常用 BeanUtils.copyProperties
的地方,替換成使用 BeanCopier
,而且封裝了一個簡單類:
public class BeanUtils { private static final Cache<String, BeanCopier> CACHE = Caffeine.newBuilder().build(); public static void copyProperties(Object source, Object target) { Class<?> sourceClass = source.getClass(); Class<?> targetClass = target.getClass(); BeanCopier beanCopier = CACHE.get(sourceClass.getName() + " to " + targetClass.getName(), k -> { return BeanCopier.create(sourceClass, targetClass, false); }); beanCopier.copy(source, target, null); } }
可是須要注意的是,BeanCopier
替換BeanUtils.copyProperties
最直接的一個問題就是:對於屬性不一樣可是名字不一樣的沒法複製。例如一個是 int 另外一個是 Integer 也不行。同時還有深拷貝的一些區別,須要咱們作好單元測試。
修改好後,問題解決。
微信搜索「個人編程喵」關注公衆號,每日一刷,輕鬆提高技術,斬獲各類offer: