问题描述
本次问题出现在一个异步接单的场景,在每天的整点会有一个定时任务触发一个批量接单的任务,会有大量的订单通过mq的方式下发到本系统。本次问题就是订单下发mq时,消费端服务器均产生频繁的fullgc,初步排查后发现问题为元空间(meta)发生了OOM(内存溢出)
相关技术栈描述
本项目在进行对象拷贝方案选型时,选用了 Orika 的方式进行对象拷贝(依赖如下),这种拷贝方式特性是比较快而且是深度拷贝,但是需要定义映射方法,核心机制就是会先创建一个映射工厂类,映射工厂通过定义好的映射方法创建一个映射类,然后通过这个映射类进行对象拷贝。本次问题产生的原因就在Orika的这种对象拷贝机制上。
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.1</version>
</dependency>
问题解析
先上一段问题代码片段,这一段的代码是用来做对象拷贝的:
/**
*出问题的代码块
*/
public static <S, D> D mapWithSubList(S source, Class<D> destClass,
String sourceSubListFiled, Class<?> sourceSubListClass, String destSubListFiled, Class<?> destSubListClass) {
if (source == null){
return null;
}
//构建映射工厂
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(source.getClass(), destClass).byDefault()
.fieldMap(sourceSubListFiled,destSubListFiled).aElementType(sourceSubListClass).bElementType(destSubListClass)
.add().register();
return mapperFactory.getMapperFacade().map(source,destClass);
}
在执行对象拷贝时会通过这个MapperFactory
创建一个MapperFacade
(映射类),然后通过这个MapperFacade
批量对对象进行拷贝处理,也就是这一段:
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapper = mapperFactory.getMapperFacade();
问题就出在这里,正常使用的时候映射类是可以重复使用的,但是由于开发者对于这个拷贝机制并不熟悉,在使用的时候新构建了映射工厂,这样就导致每次使用此拷贝方式的时候不仅会重新创建一个映射类还会创建一个映射工厂类,这样的话大量的mq请求进系统后,就会产生大量的映射类文件存储在元空间中,将元空间的内存打爆,从而触发fullgc。而且由于OOM的异常的爆出,mq的broker会判断消费失败,会重新由其他机器消费,因此所有机器无一幸免全部宕机。
解决方案
将创建工厂部分单独提出为私有静态变量,并通过静态代码块进行初始化,让其只创建一次。具体代码如下:
public class BeanMapper {
private static MapperFacade mapper;
static {
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapper = mapperFactory.getMapperFacade();
}
}
总结
在引入新的技术栈时候一定要深入了解其原理,要是早就了解此原理也不至于出现生产环境堆外OOM。