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

Java 8存储超大量对象触发堆内存溢出,求解决方案

解决Java 8中超大量对象存储的OOM问题

首先得说,你要往List里塞42亿个String对象?这简直是堆内存的噩梦啊——每个String本身就有对象头、字符数组等开销,算下来光是这些String就得占几百GB内存,普通机器根本扛不住,OOM是必然的。下面给你几个Java 8下可行的方案,按优先级排序:

1. 按需生成,完全不存储全部对象(最优解)

绝大多数场景下,你根本不需要把所有IPv4地址都存在内存里。直接在需要的时候生成单个地址,处理完就让GC回收它,内存占用几乎可以忽略。用Java 8的Stream API就能轻松实现:

// 用LongStream遍历范围,按需生成IP地址并处理
LongStream.rangeClosed(longValueFrom, longValueTo)
          .mapToObj(i -> DimNumber.getIPFromNumber(String.valueOf(i)))
          .forEach(ip -> {
              // 在这里写你的业务逻辑,比如校验、入库、写入文件等
              processYourIPLogic(ip);
          });

如果你的业务需要多次遍历这些地址,可以写一个自定义的Iterator来按需生成,避免重复计算:

class IPv4Iterator implements Iterator<String> {
    private long current;
    private final long end;

    public IPv4Iterator(long start, long end) {
        this.current = start;
        this.end = end;
    }

    @Override
    public boolean hasNext() {
        return current <= end;
    }

    @Override
    public String next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return DimNumber.getIPFromNumber(String.valueOf(current++));
    }
}

// 使用示例
Iterator<String> ipv4Iterator = new IPv4Iterator(longValueFrom, longValueTo);
while (ipv4Iterator.hasNext()) {
    String ip = ipv4Iterator.next();
    processYourIPLogic(ip);
}

这个方案的核心是避免把所有对象加载到内存,完全按需处理,内存压力极小。

2. 存储原始数据类型,减少内存开销(如果必须存储)

如果业务场景要求必须把所有数据存在内存里,那别存String,存原始的long值——每个long只占8字节,42亿个也才约32GB,比存String的几百GB省太多了。需要用的时候再转成IP字符串:

// 存储原始long值,内存开销仅为String的1/6左右
List<Long> ipv4Numbers = new ArrayList<>();
for (long i = longValueFrom; i <= longValueTo; i++) {
    ipv4Numbers.add(i);
}

// 处理时再转换为IP
for (Long num : ipv4Numbers) {
    String ip = DimNumber.getIPFromNumber(String.valueOf(num));
    processYourIPLogic(ip);
}

要是想进一步优化内存,可以用原始类型集合框架(比如Eclipse Collections的LongArrayList),它直接存储原始long值,避免了Long包装类的额外开销(每个Long对象至少16字节),能再省一半内存:

import org.eclipse.collections.impl.list.mutable.primitive.LongArrayList;

LongArrayList ipv4Numbers = new LongArrayList();
for (long i = longValueFrom; i <= longValueTo; i++) {
    ipv4Numbers.add(i);
}

ipv4Numbers.forEach(num -> {
    String ip = DimNumber.getIPFromNumber(String.valueOf(num));
    processYourIPLogic(ip);
});

3. 分批次处理+磁盘持久化(超大数据量必选)

如果32GB内存还是超出了你的机器配置,那就把数据分批写到磁盘,需要的时候再分批读取处理。比如用文本文件或者数据库存储:

// 分批次写入磁盘文件
long batchSize = 1_000_000; // 每批处理100万条
long totalCount = longValueTo - longValueFrom + 1;
long batchCount = totalCount / batchSize;
if (totalCount % batchSize != 0) {
    batchCount++;
}

try (BufferedWriter writer = new BufferedWriter(new FileWriter("ipv4_addresses.txt"))) {
    long current = longValueFrom;
    for (long batch = 0; batch < batchCount; batch++) {
        long batchEnd = Math.min(current + batchSize - 1, longValueTo);
        for (; current <= batchEnd; current++) {
            String ip = DimNumber.getIPFromNumber(String.valueOf(current));
            writer.write(ip);
            writer.newLine();
        }
        writer.flush(); // 每批写完刷新缓冲区,避免内存溢出
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 读取时逐行处理
try (BufferedReader reader = new BufferedReader(new FileReader("ipv4_addresses.txt"))) {
    String ip;
    while ((ip = reader.readLine()) != null) {
        processYourIPLogic(ip);
    }
} catch (IOException e) {
    e.printStackTrace();
}

这个方案的内存开销只限于当前批次的对象,完全不受总数据量限制,唯一的缺点是处理速度会比纯内存慢,但对于超大量数据来说是必要的妥协。

4. 调整JVM堆内存参数(不推荐,仅作最后手段)

如果你的机器有足够大的物理内存(比如256GB以上),可以尝试调整JVM堆参数来容纳所有对象。比如启动时加上:

-Xms200G -Xmx200G -XX:+UseG1GC

但要注意,这么大的堆会导致GC停顿时间极长,严重影响应用性能,而且硬件成本很高,所以只在万不得已的时候用。

为什么你的原始代码会OOM?

你的代码用ArrayList<String>存储,首先每个String的内存开销很大(对象头+字符数组,一个IPv4字符串至少占50字节),其次ArrayList初始容量很小,每次扩容都会复制整个数组,中间会产生大量临时数组,进一步消耗堆内存,最终直接撑爆。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 06:56:06