springboot实战电商我的项目mall4j (https://gitee.com/gz-yami/mall4j)

java开源商城零碎

环境:jdk1.8

零碎:window/linux

fastjson版本:1.2.29

要害代码:

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:1、设置线程栈大小、以及应用 cms垃圾回收器)

运行一段时间,会发现该 java 程序占用的内存远远大于 4g。

因为曾经设置的堆内存大小曾经限度为 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 内存状况不再急剧攀升。

springboot实战电商我的项目mall4j (https://gitee.com/gz-yami/mall4j)

java开源商城零碎