为什么这个方法打印4?

我想知道当您尝试捕获StackOverflower错误并想出以下方法时会发生什么:

类随机数生成器{
静态int cnt=0;
公共静态void main(字符串[]args){
试一试{
main(args);
}捕获(堆栈溢出错误或忽略){
System.out.println(cnt++);
}
}
}

现在我的问题是:

为什么此方法打印“4”

我想可能是因为System.out.println()。当您查看System.out.println()的源代码(和字节码)时,它通常会导致远远多于3次的方法调用(因此调用堆栈上的3段是不够的)。如果是因为Hotspot VM应用了优化(方法内联),我想知道在另一个VM上的结果是否会有所不同

编辑

由于输出似乎高度特定于JVM,因此我使用
Java(TM)SE运行时环境(build 1.6.0_41-b02)
Java HotSpot(TM)64位服务器虚拟机(构建20.14-b01,混合模式)

解释为什么我认为这个问题与理解Java堆栈不同:

我的问题不是为什么会有一个cnt>0(显然是因为System.out.println()需要堆栈大小,并在打印前抛出另一个StackOverflowerError),而是为什么它的特定值为4,分别为0、3、8、55或其他系统上的其他值

我认为其他人在解释cnt>0的原因方面做得很好,但关于cnt=4的原因以及cnt在不同设置中变化如此之大的原因,还没有足够的细节。我将尝试填补这一空白

  • X是堆栈的总大小
  • M是我们第一次进入main时使用的堆栈空间
  • R是我们每次进入main时增加的堆栈空间
  • P是运行System.out.println所需的堆栈空间

当我们第一次进入main时,剩下的空间是X-M。每个递归调用都会占用更多的内存。因此,对于1个递归调用(比原始调用多1个),内存使用是M+R。假设在C个成功的递归调用之后抛出StackOverflowerError,即M+C*R<=X和M+C*(R+1)>X。在第一次堆栈溢出错误时,还剩下X-M-C*R内存

为了能够运行System.out.prinln,我们需要堆栈上剩余的空间。如果X-M-C*R>=P,那么将打印0。如果P需要更多的空间,那么我们从堆栈中移除帧,以牺牲cnt++获得R内存

println最终能够运行时,X-M-(C-cnt)*R>=p。因此,如果p对于特定系统来说是大的,那么cnt将是大的

让我们用一些例子来看看这一点

示例1:假设

  • X=100
  • M=1
  • R=2
  • P=1

然后C=地板((X-M)/R)=49,cnt=天花板((p-(X-M-C*R))/R)=0

示例2:假设

  • X=100
  • M=1
  • R=5
  • P=12

然后C=19,cnt=2

示例3:假设

  • X=101
  • M=1
  • R=5
  • P=12

然后C=20,cnt=3

示例4:假设

  • X=101
  • M=2
  • R=5
  • P=12

然后C=19,cnt=2

因此,我们看到系统(M、R和p)和堆栈大小(X)都会影响cnt

作为旁注,启动时需要多少空间并不重要。只要没有足够的空间供捕获,那么cnt就不会增加,因此没有外部影响

编辑

我收回我所说的关于捕获的话。它确实发挥了作用。假设它需要大量的空间来启动。当剩余空间大于T时,cnt开始增加,当剩余空间大于T+P时,println运行。这增加了计算的额外步骤,并进一步混淆了已经混乱的分析

编辑

我终于有时间做一些实验来支持我的理论。不幸的是,理论似乎与实验不符。实际发生的情况非常不同

实验设置:
Ubuntu 12.04服务器,带有默认java和默认jdk。Xss从70000开始,以1字节的增量增加到460000

研究结果可在以下网址查阅:https://www.google.com/fusiontables/DataSource?docid=1xkJhd4s8biLghe6gZbcfUs3vT5MpS_OnscjWDbM
我创建了另一个版本,其中删除了每个重复的数据点。换句话说,只显示与前面不同的点。这使得更容易看到异常。https://www.google.com/fusiontables/DataSource?docid=1XG_SRzrrNasepwZoNHqEAKuZlHiAm9vbEdwfsUA

发表评论