Why does this method print 4?(为什么这个方法打印 4?)
问题描述
我想知道当您尝试捕获 StackOverflowError 并想出以下方法时会发生什么:
I was wondering what happens when you try to catch an StackOverflowError and came up with the following method:
class RandomNumberGenerator {
static int cnt = 0;
public static void main(String[] args) {
try {
main(args);
} catch (StackOverflowError ignore) {
System.out.println(cnt++);
}
}
}
现在我的问题:
为什么这个方法打印'4'?
Why does this method print '4'?
我想可能是因为 System.out.println()
在调用堆栈上需要 3 个段,但我不知道数字 3 是从哪里来的.当您查看 System.out.println()
的源代码(和字节码)时,它通常会导致比 3 多得多的方法调用(因此调用堆栈上的 3 个段是不够的).如果是因为 Hotspot VM 应用了优化(方法内联),我想知道在另一个 VM 上结果是否会有所不同.
I thought maybe it was because System.out.println()
needs 3 segments on the call stack, but I don't know where the number 3 comes from. When you look at the source code (and bytecode) of System.out.println()
, it normally would lead to far more method invocations than 3 (so 3 segments on the call stack would not be sufficient). If it's because of optimizations the Hotspot VM applies (method inlining), I wonder if the result would be different on another VM.
编辑:
由于输出似乎与 JVM 高度相关,我使用
得到结果 4Java(TM) SE 运行时环境(内部版本 1.6.0_41-b02)
Java HotSpot(TM) 64 位服务器 VM(内部版本 20.14-b01,混合模式)
As the output seems to be highly JVM specific, I get the result 4 using
Java(TM) SE Runtime Environment (build 1.6.0_41-b02)
Java HotSpot(TM) 64-Bit Server VM (build 20.14-b01, mixed mode)
解释为什么我认为这个问题与了解Java堆栈不同:b>
我的问题不是关于为什么存在 cnt > 0 (显然是因为 System.out.println()
需要堆栈大小并在打印某些内容之前抛出另一个 StackOverflowError
),但为什么它在其他系统上具有特定的值 4,分别为 0、3、8、55 或其他值.
My question is not about why there is a cnt > 0 (obviously because System.out.println()
requires stack size and throws another StackOverflowError
before something gets printed), but why it has the particular value of 4, respectively 0,3,8,55 or something else on other systems.
推荐答案
我认为其他人已经很好地解释了为什么 cnt > 0,但是没有足够的细节来解释为什么 cnt = 4,以及为什么 cnt 变化如此之大在不同的设置之间.我将尝试在这里填补这个空白.
I think the others have done a good job at explaining why cnt > 0, but there's not enough details regarding why cnt = 4, and why cnt varies so widely among different settings. I will attempt to fill that void here.
让
- X 是总堆栈大小
- M是我们第一次进入main时使用的栈空间
- R是每次进入main时增加的栈空间
- P 是运行
System.out.println
所需的堆栈空间
- X be the total stack size
- M be the stack space used when we enter main the first time
- R be the stack space increase each time we enter into main
- P be the stack space necessary to run
System.out.println
当我们第一次进入 main 时,剩下的空间是 X-M.每个递归调用占用 R 更多内存.所以对于 1 次递归调用(比原来多 1 次),内存使用量为 M + R.假设 C 递归调用成功后抛出 StackOverflowError,即 M + C * R <= X 和 M + C * (R+ 1) > X.在第一个 StackOverflowError 的时候,还剩下 X - M - C * R 内存.
When we first get into main, the space left over is X-M. Each recursive call takes up R more memory. So for 1 recursive call (1 more than original), the memory use is M + R. Suppose that StackOverflowError is thrown after C successful recursive calls, that is, M + C * R <= X and M + C * (R + 1) > X. At the time of the first StackOverflowError, there's X - M - C * R memory left.
为了能够运行 System.out.prinln
,我们需要在堆栈上留下 P 量的空间.如果碰巧 X - M - C * R >= P,那么将打印 0.如果 P 需要更多空间,那么我们从堆栈中删除帧,以 cnt++ 为代价获得 R 内存.
To be able to run System.out.prinln
, we need P amount of space left on the stack. If it so happens that X - M - C * R >= P, then 0 will be printed. If P requires more space, then we remove frames from the stack, gaining R memory at the cost of cnt++.
当 println
最终能够运行时,X - M - (C - cnt) * R >= P.因此,如果 P 对于特定系统来说很大,那么 cnt 也会很大.
When println
is finally able to run, X - M - (C - cnt) * R >= P. So if P is large for a particular system, then cnt will be large.
让我们用一些例子来看看这个.
Let's look at this with some examples.
示例1:假设
- X = 100
- M = 1
- R = 2
- P = 1
那么 C = floor((X-M)/R) = 49,cnt = ceiling((P - (X - M - C*R))/R) = 0.
Then C = floor((X-M)/R) = 49, and cnt = ceiling((P - (X - M - C*R))/R) = 0.
示例 2: 假设
- X = 100
- M = 1
- R = 5
- P = 12
那么 C = 19,cnt = 2.
Then C = 19, and cnt = 2.
示例 3: 假设
- X = 101
- M = 1
- R = 5
- P = 12
那么 C = 20,cnt = 3.
Then C = 20, and cnt = 3.
示例 4: 假设
- X = 101
- M = 2
- R = 5
- P = 12
那么 C = 19,cnt = 2.
Then C = 19, and cnt = 2.
因此,我们看到系统(M、R 和 P)和堆栈大小 (X) 都会影响 cnt.
Thus, we see that both the system (M, R, and P) and the stack size (X) affects cnt.
附带说明,启动 catch
需要多少空间并不重要.只要catch
没有足够的空间,那么cnt就不会增加,所以没有外部影响.
As a side note, it does not matter how much space catch
requires to start. As long as there is not enough space for catch
, then cnt will not increase, so there are no external effects.
编辑
我收回我所说的关于 catch
的话.它确实发挥了作用.假设它需要 T 量的空间来启动.当剩余空间大于 T 时,cnt 开始递增,当剩余空间大于 T + P 时运行 println
.这为计算增加了一个额外的步骤,进一步混淆了已经很混乱的分析.
I take back what I said about catch
. It does play a role. Suppose it requires T amount of space to start. cnt starts to increment when the leftover space is greater than T, and println
runs when the leftover space is greater than T + P. This adds an extra step to the calculations and further muddies up the already muddy analysis.
编辑
我终于有时间进行一些实验来支持我的理论.不幸的是,该理论似乎与实验不符.实际发生的情况非常不同.
I finally found time to run some experiments to back up my theory. Unfortunately, the theory doesn't seem to match up with the experiments. What actually happens is very different.
实验设置:具有默认 java 和 default-jdk 的 Ubuntu 12.04 服务器.Xss 从 70,000 开始,以 1 个字节递增到 460,000.
Experiment setup: Ubuntu 12.04 server with default java and default-jdk. Xss starting at 70,000 at 1 byte increments to 460,000.
结果位于:https://www.google.com/fusiontables/DataSource?docid=1xkJhd4s8biLghe6gZbcfUs3vT5MpS_OnscjWDbM我创建了另一个版本,其中删除了每个重复的数据点.换言之,仅显示与先前不同的点.这使得更容易看到异常.https://www.google.com/fusiontables/DataSource?docid=1XG_SRzrrNasepwZoNHqEAKuZlHiAm9vbEdwfsUA
The results are available at: https://www.google.com/fusiontables/DataSource?docid=1xkJhd4s8biLghe6gZbcfUs3vT5MpS_OnscjWDbM I've created another version where every repeated data point is removed. In other words, only points that are different from the previous are shown. This makes it easier to see anomalies. https://www.google.com/fusiontables/DataSource?docid=1XG_SRzrrNasepwZoNHqEAKuZlHiAm9vbEdwfsUA
这篇关于为什么这个方法打印 4?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!
本文标题为:为什么这个方法打印 4?
基础教程推荐
- 降序排序:Java Map 2022-01-01
- 无法使用修饰符“public final"访问 java.util.Ha 2022-01-01
- 如何使用 Java 创建 X509 证书? 2022-01-01
- 在 Libgdx 中处理屏幕的正确方法 2022-01-01
- FirebaseListAdapter 不推送聊天应用程序的单个项目 - Firebase-Ui 3.1 2022-01-01
- “未找到匹配项"使用 matcher 的 group 方法时 2022-01-01
- 设置 bean 时出现 Nullpointerexception 2022-01-01
- 减少 JVM 暂停时间 >1 秒使用 UseConcMarkSweepGC 2022-01-01
- Java:带有char数组的println给出乱码 2022-01-01
- Java Keytool 导入证书后出错,"keytool error: java.io.FileNotFoundException &拒绝访问" 2022-01-01