Java中如何清除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

