You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Java创建NGram模型时遭遇Java堆内存溢出问题求助

解决Java构建NGram模型时的堆内存溢出问题

问题背景

在大型项目的指定模块中,需用Java构建NGram模型(方案为必需项),使用JDK 20 + VS Code运行代码时持续出现堆内存溢出错误。已将堆内存调整至8GB、10GB甚至12GB,但问题仍未解决;任务管理器显示内存仅占用4-5GB,远未达到分配上限,推测问题出在数组等数据结构的内存占用逻辑上。

错误日志

初始堆内存8GB时的错误

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.HashMap.resize(HashMap.java:710)
at java.base/java.util.HashMap.putVal(HashMap.java:635)
at java.base/java.util.HashMap.put(HashMap.java:618)
at Ngram.NGramNode.addNGram(NGramNode.java:277)
at Ngram.NGramNode.addNGram(NGramNode.java:280)
at Ngram.NGram.addNGramSentence(NGram.java:157)
at com.glmadu.editdistance.TRspellChecker.getCorpus(TRspellChecker.java:68)
at com.glmadu.editdistance.TRspellChecker.checkFileSpell(TRspellChecker.java:22)
at com.glmadu.App.main(App.java:21)

调整至10GB/12GB时的错误

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space: failed reallocation of scalar replaced objects
at java.base/java.lang.String.split(String.java:3138)
at java.base/java.lang.String.split(String.java:3212)
at com.glmadu.editdistance.TRspellChecker.getCorpus(TRspellChecker.java:63)
at com.glmadu.editdistance.TRspellChecker.checkFileSpell(TRspellChecker.java:22)
at com.glmadu.App.main(App.java:21)

相关代码片段

private static void getCorpus(String output) {
        ArrayList<ArrayList<String>> corpus = new ArrayList<>();

        try (BufferedReader br = new BufferedReader(new FileReader("path/to/corpus"))) {
            String line;
            while ((line = br.readLine()) != null) {
                String[] tokens = line.split(" "); //line 63
                ArrayList<String> sentence = new ArrayList<>();
                for (String token : tokens) {
                    sentence.add(token); // line 68
                }
                corpus.add(sentence);
            }
            NGram<String> nGram = new NGram<>(corpus, 2);
            nGram.saveAsText(output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

已尝试无效方案

  • 多次增大堆内存至8GB、10GB、12GB
  • 仅读取语料库前1000行
  • 跳过NGram模型保存步骤

解决方案

1. 逐行处理语料,避免全量加载

当前代码将所有句子存入corpus集合后才构建NGram,导致内存积压大量冗余数据。改为逐行处理并即时更新NGram模型,处理完成后立即释放该行内存:

private static void getCorpus(String output) {
        // 直接初始化NGram,无需预存所有句子
        NGram<String> nGram = new NGram<>(2);

        try (BufferedReader br = new BufferedReader(new FileReader("path/to/corpus"))) {
            String line;
            while ((line = br.readLine()) != null) {
                String[] tokens = line.split(" ");
                ArrayList<String> sentence = new ArrayList<>();
                for (String token : tokens) {
                    sentence.add(token);
                }
                // 处理完一行就添加到NGram,sentence会被GC自动回收
                nGram.addNGramSentence(sentence);
            }
            nGram.saveAsText(output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

注:需确保NGram类支持动态添加句子,若原构造函数要求一次性传入全部语料,需新增addNGramSentence方法。

2. 字符串去重减少内存占用

语料库中大量重复token会占用额外内存,使用字符串池复用相同字符串:

// 方法1:使用JVM内置字符串池
sentence.add(token.intern());

// 方法2:自定义线程安全字符串池
private static final Map<String, String> STRING_POOL = new ConcurrentHashMap<>();
// ...
sentence.add(STRING_POOL.computeIfAbsent(token, k -> k));

3. 优化字符串分割逻辑

String.split(" ")在处理连续空格时会生成空字符串,且内存效率较低。改用StringTokenizer跳过空白字符:

StringTokenizer tokenizer = new StringTokenizer(line);
ArrayList<String> sentence = new ArrayList<>();
while (tokenizer.hasMoreTokens()) {
    sentence.add(tokenizer.nextToken().intern());
}

4. 验证JVM内存配置是否生效

确认VS Code的launch.json中堆内存配置格式正确:

{
    "configurations": [
        {
            "type": "java",
            "name": "App",
            "request": "launch",
            "mainClass": "com.glmadu.App",
            "vmArgs": "-Xmx8g -Xms4g" // 明确设置初始堆和最大堆
        }
    ]
}

也可在代码中打印堆内存配置验证:

Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory() / (1024 * 1024);
System.out.println("Max Heap Memory: " + maxMemory + "MB");

5. 排查大对象与内存泄漏

使用JDK自带工具分析堆内存:

  • 导出堆快照:jmap -dump:format=b,file=heapdump.hprof <进程PID>
  • 分析堆快照:jhat heapdump.hprof
    查看是否存在超长字符串、超大数组等大对象,或集合未及时释放引用导致的内存泄漏。

内容的提问来源于stack exchange,提问作者klavas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 15:59:53