springboot实战电商项目mall4j (https://gitee.com/gz-yami/mall4j)java
java开源商城系统linux
环境:jdk1.8git
系统:window/linuxspring
fastjson版本:1.2.29json
关键代码:springboot
public class FastJsonUtil { /* * 将 pojo 对象转为 json 字符串,而且驼峰命名修改成下划线命名 */ public static String buildData(Object bean) { try { SerializeConfig config = new SerializeConfig(); config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; return JSON.toJSONString(bean, config); } catch (Exception e) { return null; } } }
// 随意的一个实体类 public class T { public String userName; public String userArg; public String userGender; public String userAddr; public T(String userName, String userArg, String userGender, String userAddr) { this.userName = userName; this.userArg = userArg; this.userGender = userGender; this.userAddr = userAddr; } }
模拟并发 (也可使用 JMeter)并发
public class Main { public static void main(String[] args) throws InterruptedException { ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(6, 6, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(999999)); for (;;) { // 每隔1毫秒提交一次任务 TimeUnit.MILLISECONDS.sleep(1); poolExecutor.submit(Main::prcess); } } // 模拟一个请求调用,最终将一个 t 对象转为 json 格式返回 public static void prcess() { T t = new T("1", "2", "3", "4"); String res = FastJsonUtil.buildData(t); // System.out.printf(res); } }
jvm 启动参数:框架
-Xms4g -Xmx4g -Xmn2g -Xss1024K -XX:+UseConcMarkSweepGC
(限制堆内存大小、新时代区与老年代的比例调节为1:一、设置线程栈大小、以及使用 cms垃圾回收器)jvm
运行一段时间,会发现该 java 程序占用的内存远远大于 4g。maven
因为已经设置的堆内存大小已经限制为 4g 之内,然而 程序占用内存远超过 4g,直接往物理内存攀升,那么多是堆外内存(直接内存或者是 metaspace 的内存)的泄漏致使的。
在该程序代码中,并无对直接内存的操做,没有使用 netty 等与 io 相关的框架。
为了更加精肯定位是直接内存仍是 metaspace,追加一个启动参数 -XX:MaxDirectMemorySize=1g,重启项目,发现没有任何效果,问题依旧存在。
因此将问题锁定到 matespace 上,使用 jmap -heap pid 查看堆使用状况:
能够发现其中的 MaxMetaspaceSize 为系统内存大小,没有收到任何限制。因此多是由于调用 fastjson 的某个方法后,它处理了一些某些事情,须要将某些东西存到 metaspace 中,因为 metaspace 没有限制内存大小,致使 java 程序占用内存状况超过 4g,不断攀升,最终会引起内存预警。
尝试着在 jvm 启动参数加上 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m(通常来讲这两个值能够设置为同样,再也不赘述),再次启动项目,模拟并发,发现占用内存状况一切正常,没有超过限制值的状况出现。
因此,到这一步,能够判断出是因为 fastjson 在处理的时候可能一直加载了某个 class,致使 metaspace 内存占用过大。
在启动参数加上 -verbose:class 后再次启动项目,观察重复加载了哪一个 class
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_T from file:/E:/maven/repository/com/alibaba/fastjson/1.2.29/fastjson-1.2.29.jar] [Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_T from file:/E:/maven/repository/com/alibaba/fastjson/1.2.29/fastjson-1.2.29.jar] [Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_T from file:/E:/maven/repository/com/alibaba/fastjson/1.2.29/fastjson-1.2.29.jar] [Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_T from file:/E:/maven/repository/com/alibaba/fastjson/1.2.29/fastjson-1.2.29.jar] ...
综上,显然能够获得结论,一直在加载的是 ASMSerializer_1_T 这个 class。
接下来就是查找 fastjson 在哪里一直重复加载了这个 class。
从 JSON.toJSONString 方法开始入手,进入到 com.alibaba.fastjson.JSON#toJSONString(java.lang.Object, com.alibaba.fastjson.serializer.SerializeConfig, com.alibaba.fastjson.serializer.SerializeFilter[], java.lang.String, int, com.alibaba.fastjson.serializer.SerializerFeature...) 方法中
再依次进入 getObjectWriter(clazz) -> config.getObjectWriter(clazz) -> put(clazz, createJavaBeanSerializer(clazz)) -> createJavaBeanSerializer(beanInfo) -> createASMSerializer(beanInfo) 方法中
在 createASMSerializer 中,有这样几行代码
...... // 拼接类名 String className = "ASMSerializer_" + seed.incrementAndGet() + "_" + clazz.getSimpleName(); String packageName = ASMSerializerFactory.class.getPackage().getName(); String classNameType = packageName.replace('.', '/') + "/" + className; String classNameFull = packageName + "." + className; ClassWriter cw = new ClassWriter(); // 而后这里就加载了 ASMSerializer_ 的类 cw.visit(V1_5 // , ACC_PUBLIC + ACC_SUPER // , classNameType // , JavaBeanSerializer // , new String[] { ObjectSerializer } // ); ......
因此,就是这里,每次调用到这里,就会 load ASMSerializer_1_T 到 metaspace 中。
而这部分代码在 ASMSerializerFactory 中。
回到用户代码,在 SerializeConfig config = new SerializeConfig() 这一行,进入 SerializeConfig 的无参构造,会调用它的有参构造。有参构造中有这么几行代码
if (asm) { asmFactory = new ASMSerializerFactory(); }
一切都很清晰了,因为一直建立 SerializeConfig,致使 ASMSerializerFactory 也会被重复建立,以后 ASMSerializerFactory 再调用 本类中的 createASMSerializer 方法的时候,就会致使重复加载 com.alibaba.fastjson.serializer.ASMSerializer_1_T
解决办法:
将 SerializeConfig config = new SerializeConfig();config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
这两行代码提到方法外面,改造以下:
public class FastJsonUtil { private final static SerializeConfig config = new SerializeConfig(); static { config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; } /* * 将 pojo 对象转为 json 字符串,而且驼峰命名修改成下划线命名 */ public static String buildData(Object bean) { try { return JSON.toJSONString(bean, config); } catch (Exception e) { return null; } } }
重启项目,问题解决,不会再一直加载 ASMSerializer_1_T 这个类了。
使用 JVisualVm 观察内存活动状况
修改前的 metaspace:
修改后的 metaspace:
明显能够观察到修改后,再也不频繁装载 class 、metaspace 内存状况再也不急剧攀升。