【问题标题】:Same thread running the same recursive code seems to consume more stack memory in Java 8 compared to Java 7与 Java 7 相比,运行相同递归代码的相同线程似乎在 Java 8 中消耗更多的堆栈内存
【发布时间】:2015-12-07 21:14:50
【问题描述】:

我在“stackoverflow”站点中询问有关“java 堆栈溢出”的问题 :)

对特定输入进行一些递归函数调用的特定线程在 Oracle Java 7(64 位) 中运行良好,配置的堆栈大小为 228k (-Xss228k)。

但是,在同一堆栈的 Oracle Java 8(64 位) 中,对于同一输入运行同一递归代码的同一线程会引发 java.lang.StackOverflowError大小为 228k。如果堆栈大小增加到 512k (-Xss512k),它在 Java 8 中运行良好。

知道为什么会发生这种情况吗?与 Java 7 相比,Java 8(Hotspot JVM)是​​否进行了任何更改,这可能会增加递归函数调用的堆栈内存消耗?如果需要,我可以提供更多详细信息。

(编辑)注意:相同的递归深度在 Java 7 中“总是”有效,但在 Java 8 中“总是”失败,堆栈大小为 228k。

【问题讨论】:

  • 堆栈消耗不是完全可预测的。如果没有代码示例,就无法判断几十个可能的原因中的一个是否适用于您的特定问题。参见例如“Why is the max recursion depth I can reach non-deterministic?”
  • @Holger 相同的递归深度在 Java 7 中“总是”有效,但在 Java 8 中“总是”失败,堆栈大小为 228k。那么,非确定性因素是否仍然适用于我的场景?同时,我会尽快想出一个代码示例。
  • 你的 JVM 都是 64 位的吗?都是32位的?一个是32,另一个是64?
  • @TagirValeev 两个 JVM 都是 64 位的。

标签: memory recursion java-8 stack-overflow jvm-hotspot


【解决方案1】:

我为不同的递归场景(静态或实例方法,不同数量的 int 参数)写了a small test。这是带有-Xss228k 选项的不同版本的HotSpot JVM 64 位上的结果(StackOverflowError 发生之前的调用次数)。请注意,运行之间的数字有所不同(我使用每个 JVM 启动了两次):

          St/0  St/1  St/2  St/3  St/4  In/0  In/1  In/2  In/3  In/4
1.7.0_60  2720  2519  2309  2131  1979  2519  2309  2131  1979  1847
1.7.0_60  2716  2516  2306  2128  1976  2516  2306  2128  1976  1845
1.7.0_79  2716  2516  2306  2128  1976  2516  2306  2128  1976  1845
1.7.0_79  2729  2528  2317  2139  1986  2528  2317  2139  1986  1853
1.7.0_80  2718  2518  2308  2130  1978  2518  2308  2130  1978  1846
1.7.0_80  2738  2536  2324  2146  1992  2536  2324  2146  1992  1859
____________________________________________________________________
1.8.0_25  2818  2469  2263  2089  1940  2469  2263  2089  1940  1810
1.8.0_25  3279  2468  2262  2088  1939  2468  2262  2088  1939  1810
1.8.0_40  2714  2467  2262  2088  1938  2467  2262  2088  1938  1809
1.8.0_40  2735  2486  2279  2104  1953  2486  2279  2104  1953  1823
1.8.0_60  2729  2481  2274  2099  1949  2481  2274  2099  1949  1819
1.8.0_60  2719  2472  2266  2091  1942  2472  2266  2091  1942  1812
____________________________________________________________________
1.9_b80   2717  2470  2264  2090  1941  2470  2264  2090  1941  1811
1.9_b80   2715  2468  2263  2088  1939  2468  2263  2088  1939  1810

Instance/0Static/1 等是一样的,因为实例调用需要传递this 作为附加参数。

因此,JDK 7 和 JDK 8 之间允许的递归调用数量确实有所下降(Static/0 情况除外):您丢失了大约 30-40 个调用(大约 5%)的总数。所以可能在您的应用程序中,您非常接近极限。顺便说一句,我注意到 -Xss256k-Xss260k 之间突然跳跃(在 1.8.0_40 上测试):

          St/0  St/1  St/2  St/3  St/4  In/0  In/1  In/2  In/3  In/4
-Xss256k  2724  2476  2270  2095  1945  2476  2270  2095  1945  1816
-Xss260k  4493  3228  2959  2731  2536  3228  2959  2731  2536  2367

因此,您可以尝试将堆栈大小增加到-Xss260k,它应该足以完成您的任务。

顺便说一句,32 位 JVM 允许使用相同的 -Xss228k 进行更多调用:

          St/0  St/1  St/2  St/3  St/4  In/0  In/1  In/2  In/3  In/4
7u67_32b  7088  5078  4655  4297  3990  5078  4655  4297  3990  3724
7u67_32b  6837  5092  4667  4308  4001  5092  4667  4308  4001  3734

因此,您也有可能从 32 位 Java-7 切换到 64 位 Java-8。在这种情况下,当然需要更多的堆栈空间,因为即使堆栈中的压缩 OOP 指针似乎是 64 位,因此占用了更多空间。

【讨论】:

  • 所以您的测试没有让 JIT 有机会参与进来?如链接问题所示,它可以将结果更改六倍……
  • @Holger,出于某种原因,St/0 是不太稳定的情况(参见 1.8.0_25/St_0),并且在链接问题中进行了测试。对于 JIT 编译的代码,递归级别可能要高得多(第一次测试中最多 13000 次调用)。问题是编译器是否有足够的时间进行 JIT 编译。通常程序在第一个 StackOverflow 错误时就死了,因此我省略了对进一步启动的分析。总的来说,我的结果和你的一致。大概在 St/0 JIT-compiler 有一些机会。当然,结果可能会因更复杂的方法体(后边等)而有所不同
  • @TagirValeev 非常感谢您的详细回答!就我而言,两个 JVM 都是 64 位的。正如您所提到的,在我的场景中似乎就是这种情况:“因此,JDK 7 和 JDK 8 之间允许的递归调用数量确实有所下降(静态/0 情况除外):您丢失了大约 30-40 个调用(大约5%) 总数。所以可能在你的应用程序中你非常接近极限。顺便说一下,我注意到 -Xss256k 和 -Xss260k 之间突然跳跃。所以你可以尝试将堆栈大小增加到 -Xss260k,它应该是足够你完成任务了。”
  • @TagirValeev 我也将尝试提出一个 228K 到 512k 之间的堆栈大小,这足以满足我的线程。我很可能最终会按照您的建议将堆栈大小设置为 260k :) 顺便说一下,您是否知道为什么我们观察到 Java 7 之间允许的递归调用数量下降了 5%(大约)和Java 8?是否有任何 Java 8 文档讨论了可能导致这种退化的更改?
  • @SanjayBhat,不,我不知道。我猜它没有指定。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多