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

Java中如何清除ObjectInputStream的entries?内存占用问题求助

ObjectInputStream内存占用过高:entries数组累积问题的解决办法

问题背景

程序从文件读取无依赖的独立序列化对象,处理后写入新文件,核心代码如下:

try {
    ObjectOutputStream fileOut = new ObjectOutputStream(new FileOutputStream("outputFile"));
    ObjectInputStream fileIn = new ObjectInputStream(new FileInputStream("inputFile"));
    for(int i = 0; i < numThingsInFile; i++){
        MyObject thingToProcess = (MyObject) fileIn.readObject();
        thingToProcess.process();
        fileOut.writeObject(thingToProcess);
        fileOut.flush();
    }
    fileIn.close();
    fileOut.close();
} catch (IOException | ClassNotFoundException  e1) {
    e1.printStackTrace();
}

代码功能正常,但内存占用持续飙升,堆转储显示ObjectInputStream的entries数组异常膨胀——读取400KB文件时该数组占用超600MB内存,多线程场景下直接触发内存不足。尝试过BufferedInputStream、readUnshared()、reset()等方法,均未彻底解决,且不想拆分文件。

问题根源

ObjectInputStream的entries数组用于缓存已读取的对象引用,目的是处理序列化中的对象复用(同一对象多次写入时仅存储一次)。但此场景中所有对象都是独立无依赖的,这个缓存完全多余,且会随着读取对象数量增加持续累积,最终导致内存泄漏。

解决方案

方案1:每次读取对象后重建ObjectInputStream(最稳定)

利用文件随机访问,每次读完一个对象就关闭当前ObjectInputStream,重新打开并定位到下一个对象的起始位置。这样每次的entries数组都是全新的,不会产生累积。

代码示例:

try (RandomAccessFile raf = new RandomAccessFile("inputFile", "r")) {
    try (ObjectOutputStream fileOut = new ObjectOutputStream(new FileOutputStream("outputFile"))) {
        for (int i = 0; i < numThingsInFile; i++) {
            // 基于当前文件指针创建新的ObjectInputStream
            try (ObjectInputStream fileIn = new ObjectInputStream(new FileInputStream(raf.getFD()))) {
                MyObject thingToProcess = (MyObject) fileIn.readObject();
                thingToProcess.process();
                fileOut.writeObject(thingToProcess);
                fileOut.flush();
            }
            // 更新文件指针到下一个对象的起始位置
            raf.seek(raf.getFilePointer());
        }
    }
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

注意:此方案要求已知文件中对象的总数量(已持有numThingsInFile变量,满足条件),且每个对象的序列化数据独立存储。

方案2:自定义ObjectInputStream,手动清理entries数组

通过反射获取ObjectInputStream的entries字段,在每次读取对象后清空缓存。此方案依赖JDK内部实现,不同版本可能存在兼容性问题,需谨慎使用。

自定义流实现:

public class CleanableObjectInputStream extends ObjectInputStream {

    private final Field entriesField;

    public CleanableObjectInputStream(InputStream in) throws IOException {
        super(in);
        try {
            // 获取entries字段(OpenJDK中字段名为entries,其他JDK可能不同)
            entriesField = ObjectInputStream.class.getDeclaredField("entries");
            entriesField.setAccessible(true);
        } catch (NoSuchFieldException e) {
            throw new IOException("无法访问entries字段", e);
        }
    }

    @Override
    protected Object readObjectOverride() throws IOException, ClassNotFoundException {
        Object obj = super.readObjectOverride();
        try {
            // 清空entries缓存
            ((Map<?, ?>) entriesField.get(this)).clear();
        } catch (IllegalAccessException e) {
            throw new IOException("无法清空entries缓存", e);
        }
        return obj;
    }
}

使用示例:

try {
    ObjectOutputStream fileOut = new ObjectOutputStream(new FileOutputStream("outputFile"));
    try (CleanableObjectInputStream fileIn = new CleanableObjectInputStream(new FileInputStream("inputFile"))) {
        for(int i = 0; i < numThingsInFile; i++){
            MyObject thingToProcess = (MyObject) fileIn.readObject();
            thingToProcess.process();
            fileOut.writeObject(thingToProcess);
            fileOut.flush();
        }
    }
    fileOut.close();
} catch (IOException | ClassNotFoundException  e1) {
    e1.printStackTrace();
}

方案3:正确组合readUnshared()与reset()

之前的尝试可能未正确配合两个方法:readUnshared()读取对象时不会将其加入entries缓存,reset()则会清空已有的缓存内容。每次读取后调用reset()即可避免累积。

代码示例:

try {
    ObjectOutputStream fileOut = new ObjectOutputStream(new FileOutputStream("outputFile"));
    ObjectInputStream fileIn = new ObjectInputStream(new FileInputStream("inputFile"));
    for(int i = 0; i < numThingsInFile; i++){
        MyObject thingToProcess = (MyObject) fileIn.readUnshared();
        thingToProcess.process();
        fileOut.writeObject(thingToProcess);
        fileOut.flush();
        fileIn.reset(); // 清空已有的entries缓存
    }
    fileIn.close();
    fileOut.close();
} catch (IOException | ClassNotFoundException  e1) {
    e1.printStackTrace();
}

注意:reset()会重置流的全部状态,包括类描述符缓存,若对象涉及复杂类结构可能影响性能,但独立对象场景下无问题。

总结

优先选择方案1,无JDK版本依赖,稳定性最高;若无法频繁创建流,可考虑方案2或3。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 16:07:48