我有一个常规的64位热点JVM,其堆栈大小为1 MB。现在我试图序列化一个具有3022个父级层次结构的对象,这给了我SO(反讽)异常。
以下是一些代码:
while(epc.getParent()!=null){
epc=epc.getParent();
count++;
}
print(count);//3022
上面的代码只是告诉层次结构,但当我尝试将epc
对象序列化到ObjectOutputStream时会出现实际问题。
问题,JVM中1 MB堆栈大小的状态是什么,因为我不知道堆栈帧的大小是多少?我确定每个堆栈帧不是1KB,因为我在-Xss3000k成功运行了代码。
还有一个问题,如果我放置-Xss3000k的JVM选项,每个线程的堆栈大小是否为3000k?
答案 0 :(得分:10)
问题,JVM中1 MB堆栈大小的状态是什么,因为我不知道堆栈帧的大小是多少?
默认情况下,1 MB的默认线程堆栈大小意味着每个线程都有1MB(1048576字节)的堆栈空间。例外情况是,如果您的代码使用其中一个Thread
构造函数创建一个线程,您可以在其中提供堆栈大小参数。
堆栈帧的大小取决于被调用的方法。它需要保存方法的参数和局部变量,因此帧大小取决于它们的大小。每个帧还需要(我认为)两个额外的单词来保存已保存的帧指针和保存的返回地址。
请注意,在递归算法中,您可以为一个“递归级别”设置多个堆栈帧。对于writeObject
(在Java 8中),使用的算法是递归的,并且每个级别的数据结构通常有4个帧被序列化:
writeObject0
writeOrdinaryObject
writeSerialData
defaultWriteFields
writeObject0
etcetera
由于编译器的差异以及ObjectInputStream / ObjectOutputStream的实现发生了变化,实际的帧大小将取决于平台。您最好尝试(粗略地)测量所需的堆栈空间,而不是尝试从第一原则预测帧大小。
还有一个问题,如果我放置-Xss3000k的JVM选项,每个线程的堆栈大小是否为3000k?
是的......除了我上面所描述的。
解决您的困境的一个可能的解决方案是创建一个具有巨大堆栈的特殊线程,用于序列化。反序列化将需要具有大堆栈的类似线程。对于其余的线程,默认的堆栈大小应该没问题。
其他可能的解决方案:
实现writeReplace
和readResolve
方法,将epc对象的父结构展平为数组,这样就不会得到深度递归。 (显然,扁平/不平整需要非递归地完成。)
在调用writeObject
之前执行相同的展平等等。
使用不同的序列化机制,或者可以使用自定义机制。
答案 1 :(得分:2)
如果我放置-Xss3000k的JVM选项,每个线程的堆栈大小是否为3000k?
新线程有a constructor to specify the stack size。所有不使用该特殊构造函数的线程都将获得JVM选项中指定的默认堆栈大小(除非您自己创建它们,否则将覆盖所有线程)。
如果您的应用程序不需要大量线程,则提出的限制可能不是问题,也是最简单的解决方案。
如果没有,您可能希望构建这样一个大堆栈线程,尤其是运行深度递归序列化代码。您可以将其包装到Executor中并将应用程序代码调用到其中。
JVM中1 MB堆栈大小的状态是什么,因为我不知道堆栈帧的大小是多少?
这确实是一个移动目标。你应该让这个可配置,实验会告诉你什么是好设置。
即使是Javadoc也说
由于此构造函数的行为依赖于平台,因此在使用时应特别小心。执行给定计算所需的线程堆栈大小可能会因JRE实现而异。根据这种变化,可能需要仔细调整堆栈大小参数,并且可能需要针对应用程序运行的每个JRE实现重复调整。