序列化期间堆空间不足
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】:objOS
是 ObjectOutputStream
吗?
如果是这样,那么这就是你的问题:ObjectOutputStream
保持对曾经写入它的 每个 对象的强引用,以避免将同一个对象写入两次(它只会写一个引用说“我之前用 id x 编写的那个对象”)。
这意味着您实际上是在泄漏 all ArrayList
istances。
您可以通过在您的ObjectOutputStream
上调用reset()
来重置该“缓存”。由于您似乎没有在writeObject
调用之间使用该缓存,因此您可以在writeObject()
调用之后直接调用reset()
。
【讨论】:
+1 我没有意识到它会这样做 - 这似乎违反直觉,因为 Streams 往往被用来防止使用过多的内存。 @Bringer128:是的,这是一个不明显的功能,但非常必要:如果它没有这样做,那么序列化一个大对象树可以很容易 以无限循环结束(想想循环引用)。 有人想知道是否可以使用 WeakReferences 来完成。理论上,在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。我已经编辑了我的答案;请看一下。
干得好。我将我的反对票改为赞成票 :) 如果经过多年的编程我学到了一件事,那就是我们都在不断犯错误。愿意改正错误的人才能造就一名优秀的程序员。以上是关于序列化期间堆空间不足的主要内容,如果未能解决你的问题,请参考以下文章