JSON字符串反序列化 动态泛型

需求:定时任务扫描,反射调用目标对象,但是,方法的传参不是固定的。

方案一:将方法参数存成JSON字符串,然后JSON反序列化成对象,然后反射调用

目标方法时这样的:

CommandResp sendXXX(BaseCommandApiDTO<XXX> baseCommandApiDTO);

方式一:FastJson

Class mainBody = Class.forName(entity.getMainBodyType());
ParameterizedTypeImpl parameterizedType = new ParameterizedTypeImpl(new Type[]{mainBody}, null, BaseCommandApiDTO.class);
Object obj = JSON.parseObject(entity.getMsgText(), parameterizedType);

CommandResp resp = ReflectUtil.invoke(serviceObj, methodName, obj);

方式二:Jackson

public class ObjectMapperHolder {
    private static final ObjectMapper objectMapper = new ObjectMapper();
    public static ObjectMapper getObjectMapper() {
        objectMapper.registerModule(new Jdk8Module());
        objectMapper.registerModule(new JavaTimeModule());
        return objectMapper;
    }
}


ObjectMapper mapper = ObjectMapperHolder.getObjectMapper();
JavaType javaType = mapper.getTypeFactory().constructParametricType(BaseCommandApiDTO.class, mainBody);
Object obj = mapper.readValue(entity.getMsgText(), javaType);

CommandResp resp = ReflectUtil.invoke(serviceObj, methodName, obj);

实践中发现,这两种方式容易导致OOM

方案二:直接将参数对象存到数据库中

数据库对应字段设置BLOB类型(这里设置的是MEDIUMBLOB) ,对应的java字段类型是byte[]

直接将Java对象序列化为二进制数据,这种方式的存储和读取速度非常快,因为序列化和反序列化过程不需要转换格式。

//  写入对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(baseCommandApiDTO);
oos.flush();
byte[] data = bos.toByteArray();

//  读取对象
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(entity.getMsgObj()));
Object obj = ois.readObject();

最后的最后,优化建议:

1、尽量不要在数据库中存json字符串,如果非要存,建议字段类型设置为json,这样可以节省空间。因为你无法控制json字符串的长度,所以长度设置是个问题,另外json反序列化比较占内存,想想从一个对象变成两个对象当然要占用更多内存。

2、长度很大的字段(比如blob类型的)建议单独存一张关联表

 

补充知识:

1、一个对象序列化成JSON字符串后,这个字符串会比原对象占用更多的内存。

在序列化过程中,对象会被转换为字符串形式存储在堆内存中。由于字符串的存储方式与对象不同,通常会导致内存占用增加。具体来说,未序列化时,对象存储在堆内存中,而序列化后,这些对象被转换为字符串,字符串也需要额外的存储空间。此外,序列化过程中可能会涉及到更多的计算和内存分配,进一步增加了资源消耗‌。

序列化对内存占用的具体影响:

  • 存储空间增加‌:序列化后的字符串通常会比原对象占用更多的存储空间,因为字符串需要更多的字节来存储。例如,JSON格式的序列化会包含很多结构化的符号和字符,这些都会增加存储需求‌。
  • 计算资源消耗‌:序列化过程需要计算对象的属性和值,并将其转换为字符串形式。这个过程可能会消耗更多的CPU资源,尤其是在处理复杂对象时‌。
  • 内存占用变化‌:序列化后,对象被存储为字符串,如果对象较大或数量较多,内存占用可能会显著增加。例如,一个包含大量数据的集合在序列化后,其占用的内存可能是未序列化时的两倍或更多。

2、日志输出不当也有可能造成内存溢出

比如:log.info("请求参数: {}", JSON.toJSONString(DTO))

即使日志级别是ERROR,但是当代码执行到这一行时,仍然会执行JSON.toJSONString(DTO)这句,而将对象序列化成json字符串的这个动作会消耗一定的内存,尤其是当请求量大,而且打印的对象也很多的时候就有可能成为压垮JVM的一根稻草。

在这里log.info()起到的作用是决定要不要把日志输出到控制台或文件。

3、对于大数据量,可以使用流式解析,逐行读取和处理JSON数据,避免一次性将整个JSON加载到内存中。

 

 

参考博客 《对象序列化内存占用问题

https://www.cnblogs.com/Nuwa/p/18027475

 

posted @ 2024-12-29 11:41  废物大师兄  阅读(589)  评论(0编辑  收藏  举报