最近咱们项目升级到了 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: