1.前言介绍
商品团队在SQL维度,做了些研究,发现了:无效更新、重复SQL、同模式SQL等方向,并做了些优化工作,提高了商品和库存系统的稳定性。2020年12月,小伙伴对商品系统的前台类目缓存数据进行分析,通过集中缓存一份数据,单个JVM内存就减少约400多MB内存,引发我们对JVM内存优化的思考与探索。
2.分析思路
获取到JVM 内存dump文件,结合Eclipse Memory Analyzer 工具分析统计内存中DO、VO、String等类生成的实例数据对象的数量以及对象唯一标识,如对象Id在内存中出现的次数、对出现频率高的对象查看被那些对象持有,并分析合理性,如下图所示:
其中,对象的唯一标识的举例:数据库表的行数据,映射为Java DO对象,数据库表的主键对应的DO对象的属性字段,可作为DO对象的唯一标志,以此类推根据具体情况确定。
3.分析实践
获取到系统的JVM内存Dump文件,打开后调整到柱状图一页,如下图:
3.1 确定标识
可以发现:
1.“XXX.ctg.impl.CmCategoryCacheByVenderId59273529” 和“XXX.ctg.impl.CmCategoryCacheByVenderId” 这几个类对象,虽然只有一个实例对象,由于引用了缓存对象,从这些实例开始的 Retained Heap最大。
2.“XXX.api.domain.ctg.CatSmallClassify” 类的实例对象个数多,Retained Heap大小也最大。分析代码,这个对象与数据库表对应,其Id字段可作为实例对象的唯一标识;
以下以该类作为分析对象展开。
导出 该类的内存实例列表,通过OQL 面板 ,操作如下: 在查询 框里输入以下语句: SELECT toString(s.id) FROM XXX.domain.ctg.CatSmallClassify s 执行,然后展开所有数据,然后导出这些Id列表到文件中;
3.2 重复分析
分析这些Id的重复度,统计后,一个ID最大的重复次数为2,如下:
重复百分比为63.9087%
即23万个DO中,有14万DO是重复出现的;
3.3 对象组织结构分析
取其中一个重复的Id 如 5256202, 在OQL控制面板输入框中,输入以下语句 SELECT * FROM XXX.domain.ctg.CatSmallClassify s WHERE ((toString(s.id) = "5256202"))
通过 path 2 GC Roots,排查这两个对象被谁持有。
如图,其中一个对象被:
XXX.service.cat.impl.CatSmallClassifyServiceImpl类的 venderCodeCache 缓存容器持有
另外一个对象被:XXX.ctg.common.CatFrameworkConverter类的catSmallClassifyByVenderCache
缓存容器对象持有
3.4 缓存模型构建
其中缓存做法如下图:
CatFrameworkConverter中,维护的KV不一样,后面加了redis在JVM 和Mysql中做了一级缓存;
CatSmallClassifyServiceImpl通过维护两个缓存对象,value都是单个CatSmallClassify对象,Key是不同的组织方式。
3.5 优化分析
通过以上guava容器对CatSmallClassify 对象实例的持有,以及其中的缓存对象实际是有表的全量数据,以及CatSmallClassifyServiceImpl.cache对象的定时刷新策略,是否可以重构缓存模型,依赖一份数据,再基于本地缓存计算其他的KV关系,如下:
其中底层数据更新,引起上层KV关系的重算,由于在JVM本地内部计算,减少了网络传输等,预计是可接受,后续根据业务等其他维度,做长远考量。
优化预期:
可以发现 CatSmallClassify 实例对象占用的Retained Heap约为149MB,由上图知:
即 231053个Id 数字不一样列表,其中重复为2 的Id生成的DO对象有147663个,内存中该对象约为37.8万个。
如果去掉重复DO对象,预计每个JVM能节约:
147663/(231053+147663)*149MB= 58.0957MB的内存。
4 思考总结
以上通过内存文件,以及文件中对象的个数、重复度等,这些本身具有数量,对优化以及优化的预期效果能预知,认为是一个好的分析维度。又,其分析方法是通用的,也适用于其他项目。