记一次线上OOM的分析及处理

背景

系统调用外部接口获取数据同步至本地库,无法100%同步成功。

问题定位

查看日志后最终定位到是由于 fastjson 工具类解析 json 文件时由于文件过大到导致OOM。单独调用接口后发现返回的数据量达到了300M。

错误信息:

java.lang.OutOfMemoryError: Java heap space
    at com.alibaba.fastjson.serializer.SerializeWriter.expandCapacity(SerializeWriter.java:290)
    at com.alibaba.fastjson.serializer.SerializeWriter.writeFieldValueStringWithDoubleQuoteCheck(SerializeWriter.java:1487)
    at com.alibaba.fastjson.serializer.ASMSerializer_1_BigJsonPO.write(Unknown Source)
    at com.alibaba.fastjson.serializer.ListSerializer.write(ListSerializer.java:130)
    at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:278)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:665)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:607)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:572)

尝试解决

换解析工具

将解析工具由 fastjson 换为 hutool 中的 json 工具类后重试:失败!!!

加大内存

项目采用的时微服务架构,启动时指定了JVM堆内存初始大小及最大值:Xms300m -Xmx2048m,修改最大值参数(修改至4096m)后重启服务,再次尝试:失败!!!

最终方案

查阅资料后发现是Fastjson使用方式不对,不能直接使用JSONArray.parseArray(),当需要处理超大JSON文本时,需要使用Stream API(版本>=1.1.32)

引入依赖

 <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.7.4</version>
    </dependency>   
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.36</version>
    </dependency>

新建一个po类

用于测试json字符串与实体类之间的类型转换。

@Data
public class BigJsonPO implements Serializable {

    private static final long serialVersionUID = -63528737089874523564L;

    private String key1;
    private String key2;
    private String key3;
    ...
    private String key100;
}

模拟数据

// 模拟文件的路径   
private final String filePath = "F:\\test-json\\createByFastJson.json";
@Test
    public void testWrite() {
        TimeInterval timeInterval = new TimeInterval();
        int dataCount = 200000;
        File file = new File(filePath);
        // 需要赋值的实体类字段
        List<Field> fieldList = Arrays.stream(ReflectUtil.getFields(BigJsonPO.class)).filter(f -> !StrUtil.equalsIgnoreCase("serialVersionUID", f.getName())).collect(Collectors.toList());
        try {
            JSONWriter jsonWriter = new JSONWriter(new FileWriter(file));
            // 写入 {
            jsonWriter.startObject();
            jsonWriter.writeKey("data");
            // 写入 [
            jsonWriter.startArray();
            for (int i = 1; i <= dataCount; i++) {
                // 写入 {
                jsonWriter.startObject();
                for (int i1 = 0; i1 < fieldList.size(); i1++) {
                    String name = fieldList.get(i1).getName();
                    jsonWriter.writeKey(name);
                    jsonWriter.writeValue((i1 + 1) + "-" + IdUtil.fastSimpleUUID());
                }
                // 写入 }
                jsonWriter.endObject();
            }
            // 写入 ]
            jsonWriter.endArray();
            // 写入 }
            jsonWriter.endObject();
            jsonWriter.close();
            System.out.println("文件大小:【" + FileUtil.size(file) / (1024 * 1024) + "M】,写入数据:【" + dataCount + "】条,耗时:【" + timeInterval.intervalMs() + "】ms");
        } catch (IOException e) {
            e.printStackTrace();
        }

读取数据

    @Test
    public void testParse() {
        TimeInterval timeInterval = new TimeInterval();
        try {
            File file = new File(filePath);
            List<BigJsonPO> list = this.parseBigJsonStr(new FileReader(file), "data", BigJsonPO.class);
            System.out.println("文件大小:【" + FileUtil.size(file) / (1024 * 1024) + "M】,解析数据:【" + list.size() + "】条,耗时:【" + timeInterval.intervalMs() + "】ms");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

/**
     * 解析 大 json 对象,格式:
     * {
     *      jsonArrKey :[ "key1":"value1"...]
     * }
     * @param reader
     * @param jsonArrKey
     * @param jsonArrKeyType
     * @param <T>
     * @return
     */
    private <T> List<T> parseBigJsonStr(Reader reader, String jsonArrKey, Class<T> jsonArrKeyType) {
        List<T> list = new ArrayList<>();
        try {
            JSONReader jsonReader = new JSONReader(reader);
            jsonReader.startObject();
            while (jsonReader.hasNext()) {
                String tempJsonKey = jsonReader.readObject(String.class);
                if (StrUtil.equalsIgnoreCase(jsonArrKey, tempJsonKey)) {
                    jsonReader.startArray();
                    while (jsonReader.hasNext()) {
                        list.add(jsonReader.readObject(jsonArrKeyType));
                    }
                    jsonReader.endArray();
                }
            }
            jsonReader.endObject();
            jsonReader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

至此,问题完美解决。

其他解决方案

搜了资料后发现, jackson 类库也可以解决问题,代码类似。

引入依赖

 <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.7.4</version>
    </dependency>  

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.13.1</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.13.1</version>
    </dependency>

模拟数据

 @Test
    public void testWriteJson() {
        TimeInterval timeInterval = new TimeInterval();
        int dataCount = 100000;
        this.writeJson(filePath, dataCount, BigJsonPO.class);
        System.out.println("写入数据量:" + dataCount + ",耗时:" + timeInterval.intervalMs());
    }

    private <T> void writeJson(String filePath, long dataCount, Class<T> poClass) {
        JsonFactory factory = JsonFactory.builder().build();
        try {
            List<Field> fieldList = Arrays.stream(ReflectUtil.getFields(poClass)).filter(f -> !StrUtil.equalsIgnoreCase("serialVersionUID", f.getName())).collect(Collectors.toList());
            JsonGenerator jg = factory.createGenerator(new File(filePath), JsonEncoding.UTF8);
            jg.writeStartObject();
            jg.writeFieldName("data");
            jg.writeStartArray();
            for (long i = 0; i < dataCount; i++) {
                jg.writeStartObject();
                for (int j = 0; j < fieldList.size(); j++) {
                    String fieldName = fieldList.get(j).getName();
                    Object value = (j + 1) + "-" + IdUtil.fastSimpleUUID();
                    jg.writeObjectField(fieldName, value);
                }
                jg.writeEndObject();
            }
            jg.writeEndArray();
            jg.writeEndObject();
            jg.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

读取数据

@Test
    public void testParseJson() {
        TimeInterval timeInterval = new TimeInterval();
        JsonFactory factory = JsonFactory.builder()
                .build().setCodec(new ObjectMapper());
        List<BigJsonPO> list = new ArrayList<>();
        File file = new File(filePath);
        try {
            JsonParser jp = factory.createParser(file);
            JsonToken outToken = jp.nextToken();
            // 校验格式是否正确
            if (outToken != JsonToken.START_OBJECT) {
                throw new RuntimeException("Error json string");
            }
            // 获取当前节点名称
            String currentName;
            // 循环 直至 结尾 符号 出现
            while (jp.nextToken() != JsonToken.END_OBJECT) {
                currentName = jp.getCurrentName();
                if (jp.nextToken() == JsonToken.START_ARRAY) {
                    if (currentName.equals("data")) {
                        while (jp.nextToken() != JsonToken.END_ARRAY) {
                            BigJsonPO po = jp.readValueAs(BigJsonPO.class);
                            list.add(po);
                        }
                    } else {
                        jp.skipChildren();
                    }
                } else {
                    jp.skipChildren();
                }
            }
            jp.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件大小:【" + FileUtil.size(file) / (1024 * 1024) + "M】,解析数据:【" + list.size() + "】条,耗时:【" + timeInterval.intervalMs() + "】ms");
    }

性能对比

本机处理器: ADM 4700U 8核 16G

fastjson VS jackson

数据量(W)数据大小(M)写入耗时(ms)解析耗时(ms)
0.28.74316/198298/338
287.41397/1007937/848
2087412103/82249479/9992
2008430105898/79756暂未测试
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇