避免 OutOfMemoryError

Avoiding OutOfMemoryError

我有一个代码,其中我得到了一个大的 JSON 字符串(可以是 50MB 到 250MB 之间的任何地方),它是一个 JSON 对象的数组,要被解析和清理然后序列化为文件。使用 50MB JSON 字符串一切正常,但是当字符串超过 100 MB 左右时,我的应用程序因 OutOfMemoryError 而崩溃。我知道我可以增加堆的大小,但如果可能的话我想避免这样做。我已经包含了我最近的一些想法。我尝试稍微移动 try 块但无济于事。

1) 我怀疑有一些方法可以用流来做到这一点,但我不知道如何流式传输结果字符串(它是 json 对象的 json 数组字符串)一个 JSON 个对象。

2) 由于结果是 Java 字符串,因此它是不可变的。我们如何使用这个字符串并尽快将其从内存中取出?

3) cleanedResult 每次都实例化一个新对象会更好,而不是每次都为同一个对象分配不同的东西吗?

4) 在 for 循环的末尾,不应该只使用大约 2 倍于循环之前的内存,因为现在 json stringbuilder 变量包含与结果字符串相同的内存,应该是两个内存中最大的变量?

我已经包含了下面的代码。

String result = getLargeJSONString(...); // function that gives me a large JSON string which is an array of JSON objects
StringBuilder json = new StringBuilder(); // to hold final JSON values to write to file

// try to parse said large JSON String
JSONArray results = new JSONArray();
try {
  results = new JSONArray(result);
} catch (JSONException j) {
  j.printStackTrace();
}

// do json sanitation on each object and then append to stringbuilder
// note the final result should be a string with a JSON object on each newline
JSONObject cleanedResult = new JSONObject();
for (int i = 0; i < results.length(); i++) {
  try {
    cleanedResult = JSONSanitizer.sanitize((JSONObject) results.get(i));
  } catch (JSONException j) {
    cleanedResult = new JSONObject();
  }
  json.append(cleanedResult.toString());
  json.append('\n');
}

// write built string to file
try {
  Files.write(Paths.get("../file.json"), json.toString().getBytes());
} catch (IOException i) {
  System.out.println(i);
}

在 corse 中,您应该更喜欢流式传输而不是连续内存分配(String、StringBuilder、数组等)来处理大量数据。所以你最好的机会是使用流媒体 JSON parser/serializer.

但是,您应该首先尝试通过几个容易获得的修复来优化您的代码:

One:如果您确实需要在将结果写入文件之前存储结果,请将 StringBuilder 的大小预先设置为它的估计最大最终大小,因此它会获胜' 需要在每次执行 append 时调整大小。例如,像这样:

StringBuilder json = new StringBuilder(result.length());

你甚至最好考虑换行符的额外大小。例如超大 5%:

StringBuilder json = new StringBuilder((int)(1.05d*result.length()));

:如果您只需要将结果写入文件,甚至不要将其存储到 StringBuilder 中:

String result = getLargeJSONString(...);
JSONArray results = new JSONArray(result);
try(Writer output=new OutputStreamWriter(new FileOutputStream(outputFile), "UTF8")) {
    for (int i = 0; i < results.length(); i++) {
        JSONObject cleanedResult = JSONSanitizer.sanitize((JSONObject) results.get(i));
        output.write(cleanedResult.toString());
        output.write('\n');
    }
}