JVM 堆栈大小规范

JVM stack size specifications

我有一个常规的 64 位热点 JVM,它的堆栈大小为 1 MB。现在我试图序列化一个具有 3022 个父级层次结构的对象,这给了我 SO(讽刺)异常。

这是一些代码:

while(epc.getParent()!=null){

      epc=epc.getParent();
      count++;
     }
     print(count);//3022

上面的代码只是为了告诉层次结构,但当我尝试将 epc 对象序列化到 ObjectOutputStream 时,实际问题出现了。

问题,1 MB 堆栈大小在 JVM 中表示什么,因为我不知道堆栈帧的大小是多少?我确定它不是每个堆栈帧 1KB,因为我 运行 在 -Xss3000k 成功地编写了代码。

还有一个问题,如果我将 JVM 选项设置为 -Xss3000k,是否每个线程都有 3000k 堆栈大小?

Does every thread has 3000k stack size if I put a JVM option of -Xss3000k ?

新线程有 a constructor to specify the stack size。所有不使用该特殊构造函数的线程都将获得 JVM 选项中指定的默认堆栈大小(这将覆盖所有线程,除非您自己明确创建它们)。

如果您的应用程序不需要大量线程,提高限制可能不是问题,也是最简单的解决方案。

如果没有,您可能想要构建这样一个大堆栈线程,特别是 运行 您的深度递归序列化代码。您可以将它包装到一个执行器中,并让应用程序代码调用它。

what does 1 MB stack size states in JVM as I have no idea what size a stack frame of?

这确实是一个移动的目标。您应该将此设置为可配置的,实验会告诉您什么是好的设置。

连 Javadoc 都说

Due to the platform-dependent nature of the behavior of this constructor, extreme care should be exercised in its use. The thread stack size necessary to perform a given computation will likely vary from one JRE implementation to another. In light of this variation, careful tuning of the stack size parameter may be required, and the tuning may need to be repeated for each JRE implementation on which an application is to run.

Question, what does 1 MB stack size states in JVM as I have no idea what size a stack frame of?

1 MB 默认线程堆栈大小意味着每个线程都有 1MB(1048576 字节)的堆栈 space ... 默认情况下。例外情况是,如果您的代码使用 Thread 构造函数之一创建线程,您可以在其中提供堆栈大小参数。

堆栈帧的大小取决于调用的方法。它需要保存方法的参数和局部变量,因此帧大小取决于它们的大小。每个帧还需要(我认为)两个额外的字来保存保存的帧指针和保存的 return 地址。

请注意,在递归算法中,一个 "level of recursion" 可以有多个堆栈帧。对于writeObject(在Java 8),使用的算法是递归的,被序列化的数据结构的每一层通常有4帧:

writeObject0 
writeOrdinaryObject
writeSerialData
defaultWriteFields
writeObject0
etcetera

由于编译器的差异以及 ObjectInputStream / ObjectOutputStream 实现的变化,实际帧大小将取决于平台。您最好尝试(粗略地)测量所需的堆栈 space 而不是尝试根据第一原理预测帧大小。

One more question, does every thread has 3000k stack size if I put a JVM option of -Xss3000k ?

是的......除了我上面描述的例外。

解决您的困境的一个可能解决方案是创建一个具有巨大堆栈的特殊线程,用于序列化。反序列化将需要一个具有巨大堆栈的类似线程。对于其余线程,默认堆栈大小应该没问题。

其他可能的解决方案:

  • 实施writeReplacereadResolve 方法将epc 对象的父结构展平为一个数组,这样您就不会进行深度递归。 (显然,展平/反展平需要非递归地完成。)

  • 在调用 writeObject 之前 执行相同的扁平化,等等。

  • 使用不同的序列化机制,或者可能使用自定义序列化机制。