java - 冗余方法调用减少了可用堆栈内存
问题描述
我在实际项目中遇到了 StackOverflowError,并制作了简单的模型来显示问题。它是调用一些递归方法并保存错误深度的测试类。
public class Main {
static int c = 0;
public static void main(String[] args) {
long sum = 0;
int exps = 100;
for (int i = 0; i < exps; ++i) {
c = 0;
try {
simpleRecursion();
} catch (StackOverflowError e) {
sum += c;
}
}
System.out.println("Average method call depth: " + (sum / exps));
}
public static void simpleRecursion() {
simpleMethod();
++c;
simpleRecursion();
}
}
simpleMethod 有两个版本:
public static void simpleMethod() {
}
- 它在测试中获得 51K 或 59K 方法调用的深度。
public static void simpleMethod() {
c += 0;
}
- 它在测试中获得 48K 或 58K 方法调用的深度。
为什么这些认识会得到不同的结果?在第二种情况下,我无法理解堆栈中有哪些额外数据。我认为 simpleMethod 不应该影响堆栈内存,因为它不在调用链中。
解决方案
由于性能原因,您遇到的问题可能与 JVM 的内联方法有关。内联方法可能会影响为该方法分配的堆栈大小。您可以检查方法的堆栈大小有javap -v
多大,该堆栈大小在调用该方法时被分配。对于您的代码,结果javap -v
如下:
simpleRecursion()
方法:
public static void simpleRecursion();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: invokestatic #13 // Method simpleMethod:()V
3: getstatic #2 // Field c:I
6: iconst_1
7: iadd
8: putstatic #2 // Field c:I
11: invokestatic #3 // Method simpleRecursion:()V
14: return
LineNumberTable:
line 19: 0
line 20: 3
line 21: 11
line 22: 14
simpleMethod()
不带线的方法c+=0;
:
public static void simpleMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 25: 0
simpleMethod();
带线的方法c+=0;
:
public static void simpleMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field c:I
3: iconst_0
4: iadd
5: putstatic #2 // Field c:I
8: return
LineNumberTable:
line 25: 0
line 26: 8
带有空主体的方法变体需要 的堆栈大小0
,而带有行的方法变体c+=0;
需要的堆栈大小为2
。
我的猜测是,当该方法simpleMethod()
被simpleRecursion()
JVM/JIT/HotSpot 内联时(请参阅其他问题,如Java 中有内联函数吗?或优化期间 Java 内联方法吗?simpleRecursion()
)它会增加堆栈大小所需的额外堆栈大小的空间simpleMethod()
。现在 的堆栈大小simpleRecursion()
更大,这导致StackOverflowError
更早达到极限。
不幸的是,我无法验证这一点,因为涉及到 JIT/HotSpot。哎呀,即使多次运行同一个应用程序,c
最后也会产生不同的值。当我尝试使用使用 代替方法调用的simpleRecursion()
变体时,堆栈大小保持不变,很可能是因为编译器足够聪明,可以使用相同的堆栈大小.c+=0;
simpleMethod();
2
推荐阅读
- eclipse - Eclipse - 来自外部工具控制台输出的问题标记
- symfony-forms - Symfony 表单集合 - 保持与主键的关联
- docker - 尝试使用 bitbucket 管道在 docker 中调用服务的 url
- google-app-engine - Google App Engine - 使用外部目录中的种子文件部署?
- python - 从 urllib 和 Postman 调用时,Flask 服务器收到的 POST 请求数据的差异
- vue.js - vue-cli 默认单元测试“Unexpected token {”错误
- java - 将 EventFiringWebDriver 与 RemoteWQebDriver 一起使用
- c# - https Web 服务中缺少 _vti_bin/ListData.svc/$metadata
- android - 微调器下拉在约束布局中的底部导航视图上重叠
- sharepoint - SharePoint - 显示修改的讨论列表项的重复搜索结果