最近碰到了两个内存泄漏问题,都是fastjson引起的,因此这里做一下简单记录,一个是序列化引起,另外一个是反序列化引起
序列化问题导致堆外metaspace内存泄漏
问题代码如下:
SerializeConfig config = new SerializeConfig();
config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
String msg = JSONObject.toJSONString(pushMsg, config);
ProducerResult producerResult = processor.sendMessage(msg);
主要使用到了SerializeConfig这个类,造成的影响就是metaspace内存增加。原因是SerializeConfig的构造方法中默认会使用asm,然后创建ASMSerializerFactory,然后创建代理类,如果每次都new一个新的SerializeConfig,就会导致频繁创建代理类(如com.alibaba.fastjson.serializer.ASMSerializer_1_XXX),而metaspace保存的就是类的元信息,就会造成metaspace增加
try {
if (this.asm) {
this.asmFactory = new ASMSerializerFactory();
}
} catch (Throwable var4) {
this.asm = false;
}
。。。
JavaBeanSerializer serializer = this.asmFactory.createJavaBeanSerializer(beanInfo);
。。。
createJavaBeanSerializer中
String className = "ASMSerializer_" + this.seed.incrementAndGet() + "_" + clazz.getSimpleName();
解决办法:
将SerializeConfig作为类的静态变量
private static final SerializeConfig CONFIG = new SerializeConfig();
static {
CONFIG.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
}
反序列化问题导致堆内存泄漏
问题代码如下:
return JSONObject.parseObject("xxx",
new TypeToken<Set<String>>() {
}.getType());
分析内存一般是 com.alibaba.fastjson.util.IdentityHashMap$Entry对象比较大,或者是
com.alibaba.fastjson.parser.ParserConfig
原因:
new TypeToken<Set<String>>() {
}.getType() 这个每次都会生成一个type,而DefaultJSONParser在反序列化时使用到了ParserConfig的getDeserializer方法来获取反序列化器,get获取时key就是type,而此处是根据hashcode去取,就导致每次都无法命中,每次都会新生成一个序列化器,导致内存越来越大
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
//parseObject中 根据type获取反序列化器
ObjectDeserializer deserializer = this.config.getDeserializer(type);
// 具体get方法
public final V get(K key) {
int hash = System.identityHashCode(key);
int bucket = hash & this.indexMask;
for(IdentityHashMap.Entry entry = this.buckets[bucket]; entry != null; entry = entry.next) {
if (key == entry.key) {
return entry.value;
}
}
return null;
}
// 注释内容:无论给定的x对象是否覆盖了hashCode()方法,都会调用默认的hashCode()方法返回hashCode,如果x == null, 返回0。
// 这个默认的hashCode()方法就是Object类中的hashCode方法。
// 这说明默认的hashCode方法是根据对象的地址转换所得到的。每次new出来的新对象的地址都是不同,所以会出现这个问题
/**
* Returns the same hash code for the given object as
* would be returned by the default method hashCode(),
* whether or not the given object's class overrides
* hashCode().
* The hash code for the null reference is zero.
*
* @param x object for which the hashCode is to be calculated
* @return the hashCode
* @since JDK1.1
*/
public static native int identityHashCode(Object x);
可能使用到的命令
jps 找到服务相关的pid
jmap查看JVM对象信息
jmap -histo pid
增加jvm启动参数:-XX:+TraceClassLoading -XX:+TraceClassUnloading 查看项目的类加载日志