序列化期间堆空间不足

Posted

技术标签:

【中文标题】序列化期间堆空间不足【英文标题】:Out of heap space during serialization 【发布时间】:2011-11-21 15:34:58 【问题描述】:

以下代码导致大约 300 万行出现OutOfMemmoryError: heap space

分配给 JVM 的内存为 4 GB,使用 64 位安装。

while (rs.next())
    
    ArrayList<String> arrayList = new ArrayList<String>();
    for (int i = 1; i <= columnCount; i++)
    
        arrayList.add(rs.getString(i));
    

    objOS.writeObject(arrayList);

ArrayList 引用的内存在 while 循环的每次迭代中都有资格进行垃圾回收,并且由于堆空间问题,在抛出 OutOfMemoryError 之前,JVM 在内部调用垃圾回收 (System.gc())。

那么为什么会发生异常呢?

【问题讨论】:

@Swaranga Sarma 在同一时间编辑,不确定帖子是否混乱 【参考方案1】:

objOSObjectOutputStream 吗?

如果是这样,那么这就是你的问题:ObjectOutputStream 保持对曾经写入它的 每个 对象的强引用,以避免将同一个对象写入两次(它只会写一个引用说“我之前用 id x 编写的那个对象”)。

这意味着您实际上是在泄漏 all ArrayList istances。

您可以通过在您的ObjectOutputStream 上调用reset() 来重置该“缓存”。由于您似乎没有在writeObject 调用之间使用该缓存,因此您可以在writeObject() 调用之后直接调用reset()

【讨论】:

+1 我没有意识到它会这样做 - 这似乎违反直觉,因为 Streams 往往被用来防止使用过多的内存。 @Bringer128:是的,这是一个不明显的功能,但非常必要:如果它没有这样做,那么序列化一个大对象树可以很容易 以无限循环结束(想想循环引用)。 有人想知道是否可以使用 Wea​​kReferences 来完成。理论上,在writeObject 返回之前,正在写入的对象不会超出范围。 @Bringer128:是的,这是可行的。没有这样做的原因可能是 ObjectOutputStream 是在 Java 1.1 中引入的,而 WeakReference 是在 Java 1.2 中引入的。将ObjectOutputStream 实现更改为使用WeakReference肯定是一个突破性的变化。 不错的发现!我现在删除了我的答案,因为该链接解释了 writeUnshared 的深度不深,因此可能对 OP 的问题没有用。【参考方案2】:

我同意@Joachim。

以下建议是一个神话

此外,建议(按照良好的编码约定)不要在循环内声明任何对象。相反,在循环开始之前声明它并使用相同的引用进行初始化。这将要求您的代码对每次迭代使用相同的引用,并减少内存释放线程(即垃圾收集)的负担。

真相 我已经编辑了这个,因为我觉得可能有很多人(比如今天之前的我)仍然认为在循环中声明一个对象可能会损害内存管理;这是错误的。 为了证明这一点,我使用了发布在 *** for this 上的相同代码。 以下是我的代码 sn-p

package navsoft.advskill.test;

import java.util.ArrayList;

public class MemoryTest 

    /**
     * @param args
     */
    public static void main(String[] args) 
        /* Total number of processors or cores available to the JVM */
        System.out.println("Available processors (cores): "
                + Runtime.getRuntime().availableProcessors());
        /*
         * Total amount of free memory available to the JVM
         */
        long freeMemory = Runtime.getRuntime().freeMemory();
        System.out.println("Free memory (bytes): "
                + freeMemory);
        /*
         * This will return Long.MAX_VALUE if there is no preset limit
         */
        long maxMemory = Runtime.getRuntime().maxMemory();
        /*
         * Maximum amount of memory the JVM will attempt to use
         */
        System.out.println("Maximum memory (bytes): "
                + (maxMemory == Long.MAX_VALUE ? "no limit" : maxMemory));
        /*
         * Total memory currently in use by the JVM
         */
        System.out.println("Total memory (bytes): "
                + Runtime.getRuntime().totalMemory());
        final int LIMIT_COUNTER = 1000000;
        
        //System.out.println("Testing Only for print...");
        System.out.println("Testing for Collection inside Loop...");
        //System.out.println("Testing for Collection outside Loop...");
        //ArrayList<String> arr;
        for (int i = 0; i < LIMIT_COUNTER; ++i) 
            //arr = new ArrayList<String>();
            ArrayList<String> arr = new ArrayList<String>();
            System.out.println("" + i + ". Occupied(OldFree - currentFree): "+ (freeMemory - Runtime.getRuntime().freeMemory()));
        
        System.out.println("Occupied At the End: "+ (freeMemory - Runtime.getRuntime().freeMemory()));
        System.out.println("End of Test");
    


输出结果清楚地表明,如果您在循环内部或外部声明对象,占用/释放内存没有区别。因此,建议将声明的范围尽可能小。 感谢 *** 上的所有专家(特别是 @Miserable Variable)在这方面的指导。

希望这也能消除您的疑虑。

【讨论】:

我不相信这会产生任何影响。事实上,我高度怀疑编译器会在每次循环迭代中为arrayList 重复使用相同的局部变量槽,这相当于“在循环外声明它”。 -1 什么?建议总是声明具有最小范围的变量。你能找到任何参考资料吗? 我认为这是过早的优化并且非常危险。限制范围具有已知的好处,时间是在C 中,所有变量都(必须)在函数顶部声明。我们不再那样做。见***.com/questions/377763/…。我会敦促您更改编码约定。 谢谢@HemalPandya。我已经编辑了我的答案;请看一下。 干得好。我将我的反对票改为赞成票 :) 如果经过多年的编程我学到了一件事,那就是我们都在不断犯错误。愿意改正错误的人才能造就一名优秀的程序员

以上是关于序列化期间堆空间不足的主要内容,如果未能解决你的问题,请参考以下文章

Google Protobuf '编译器堆空间不足'

Eclipse 堆空间(内存不足错误)

内存不足:Java 堆空间,但在查看堆空间时最大使用 50 MB

Kafka Connect 堆空间不足

理解 JVM 内存分配和 Java 内存不足:堆空间

内存不足错误,java堆空间