文章目录
- 1、背景
- 2、快照文件分析
- 3、本地环境复现
- 4、结论
- 5、解决方式
1、背景
文章微服务升级,新增了一个传入文章id的List,判断有多少id是存在的接口,第二天高峰期内存溢出。
2、快照文件分析
打开直方图,发现线程对象占用排第一。打开支配树,按深堆排序,选占用最大的线程对象,找到处理器方法HandlerMethod,List objects --> with outgoing references查看其关联的对象
在description中方找到当前线程在执行哪个方法
再回到支配树,发现有个字符串对象,深堆非常大,里面是一句SQL,而字符串底层是用字符数组实现的,其下方的char有157w大小,且char的浅堆也很大,点击发现里面是frch_item,而它的产生是因为SQL在拼接过程中用了foreach去遍历了一个大集合:
3、本地环境复现
找到快照文件中的相关代码:
@RestController
@RequestMapping("/sqljoint")
public class DemoSqlJointController {
/**
* 服务对象
*/
@Resource
private TbArticleService articleService;
/**
* 判断批量id存在多少个
* size:传入生成的id数量
*/
@GetMapping
public ResponseEntity countIfAbsent(int size) {
//随机生成批量id,模拟传入一个id的List
List<Integer> ids = new Random().ints(0, 1000000).
limit(size).boxed().collect(Collectors.toList());
return ResponseEntity.ok(this.articleService.countIfAbsent(ids));
}
}
Mapper层用foreach做了一个遍历
<!--判断当前id在不在数据库中,在就记为1,不在就记为0-->
<select id="countIfAbsent" resultType="java.lang.Long">
select
IFNULL(sum(1),0)
from article where
<if test="ids != null and ids.size() > 0">
id in
<foreach collection="ids" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
</select>
在本地启动Jmeter,调整size的值
Visual发现size大时,JVM堆内存溢出
4、结论
Mybatis在使用foreach进行sql拼接时,会在内存中创建对象,如果foreach处理的数组或者集合元素个数过多,会占用大量的内存空间。
5、解决方式
- 限制id个数的最大值
- 将id缓存到redis,先用内存查