为什么此方法打印4?

我想知道当你试图捕捉一个StackOverflowError时会发生什么,并提出以下方法:

class RandomNumberGenerator {

    static int cnt = 0;

    public static void main(String[] args) {
        try {
            main(args);
        } catch (StackOverflowError ignore) {
            System.out.println(cnt++);
        }
    }
}

现在我的问题:

为什么这种方法打印'4'?

我想也许是因为System.out.println()需要调用堆栈中的3段,但我不知道数字3来自哪里。 查看System.out.println()的源代码(和字节码)时,通常会导致比3更多的方法调用(因此调用堆栈上的3个段不足以满足要求)。 如果是因为热点虚拟机应用的优化(方法内联),我想知道另一个虚拟机的结果会不同。

编辑:

由于输出看起来与JVM高度相关,所以我得到了使用的结果
Java(TM)SE运行时环境(build 1.6.0_41-b02)
Java HotSpot(TM)64位服务器虚拟机(构建20.14-b01,混合模式)


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

我的问题不是关于为什么有一个cnt> 0(显然是因为System.out.println()需要堆栈大小,并在打印之前抛出另一个StackOverflowError ),但为什么它具有特殊值4,分别为0,3, 8,55或其他系统上的其他内容。


我认为其他人在解释为什么cnt> 0方面做得很好,但是为什么cnt = 4没有足够的细节,以及为什么cnt在不同环境中变化很大。 我会尽力填补这个空白。

  • X是总堆栈大小
  • M是第一次进入main时使用的堆栈空间
  • R是每次进入main时增加的堆栈空间
  • P是运行System.out.println所需的堆栈空间
  • 当我们第一次进入主体时,剩下的空间就是XM。 每次递归调用都会占用更多的内存。 因此,对于1次递归调用(比原来多1次),内存使用是M + R.假设在C成功递归调用之后抛出StackOverflowError,即M + C * R <= X且M + C *(R + 1)> X.在第一个StackOverflowError的时候,剩下了X - M - C * R内存。

    为了能够运行System.out.prinln ,我们需要在堆栈上留下P个空间。 如果碰巧X-M-C * R> = P,那么将打印0。 如果P需要更多空间,那么我们从堆栈中移除帧,以c ++ ++为代价获得R内存。

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

    我们来看一些例子。

    例1:假设

  • X = 100
  • M = 1
  • R = 2
  • P = 1
  • 那么C = floor((XM)/ R)= 49,并且cnt = ceiling((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。

    作为一个侧面说明, catch空间需要多少空间并不重要。 只要没有足够的catch空间,cnt就不会增加,所以没有外部影响。

    编辑

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

    编辑

    我终于找到时间运行一些实验来支持我的理论。 不幸的是,这个理论似乎并不符合实验。 实际发生的事情是非常不同的。

    实验设置:使用默认java和default-jdk的Ubuntu 12.04服务器。 XSS从70,000开始,以1字节为增量增加到460,000。

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


    这是糟糕的递归调用的受害者。 正如你想知道为什么cnt的值会变化,这是因为栈的大小取决于平台。 Windows上的Java SE 6在32位虚拟机中的默认堆栈大小为320k,在64位虚拟机中的默认堆栈大小为1024k。 你可以在这里阅读更多。

    您可以使用不同的堆栈大小运行,您会在堆栈溢出之前看到不同的cnt值 -

    java -Xss1024k RandomNumberGenerator

    即使该值大于1,也不会看到多次打印cnt的值,因为您的打印语句也会抛出错误,您可以通过Eclipse或其他IDE来确认是否可以进行调试。

    如果您愿意,可以将代码更改为以下代码以调试每个语句的执行情况 -

    static int cnt = 0;
    
    public static void main(String[] args) {                  
    
        try {     
    
            main(args);   
    
        } catch (Throwable ignore) {
    
            cnt++;
    
            try { 
    
                System.out.println(cnt);
    
            } catch (Throwable t) {   
    
            }        
        }        
    }
    

    更新:

    随着这一点得到更多的关注,让我们举另一个例子来说明问题 -

    static int cnt = 0;
    
    public static void overflow(){
    
        try {     
    
          overflow();     
    
        } catch (Throwable t) {
    
          cnt++;                      
    
        }
    
    }
    
    public static void main(String[] args) {
    
        overflow();
        System.out.println(cnt);
    
    }
    

    我们创建了另一个名为overflow的方法来执行错误的递归,并从catch块中移除了println语句,因此它在尝试打印时不会引发另一组错误。 这按预期工作。 你可以尝试把System.out.println(cnt); 在cnt ++之后的语句并编译。 然后运行多次。 根据您的平台,您可能会得到不同的cnt值。

    这就是为什么我们通常不会发现错误,因为代码中的神秘不是幻想。


    行为取决于堆栈大小(可以使用Xss手动设置堆栈大小,堆栈大小是特定于架构的。从JDK 7源代码:

    // Windows上的默认堆栈大小由可执行文件(java.exe
    //默认值为320K / 1MB [32位/ 64位])。 取决于Windows版本,更改
    // ThreadStackSize为非零可能会对内存使用产生重大影响。
    //请参阅os_windows.cpp中的注释。

    所以当引发StackOverflowError时,错误会被catch块捕获。 这里println()是另一个堆栈调用,它再次抛出异常。 这得到重复。

    它重复多少次? - 这取决于JVM何时认为它不再是stackoverflow。 这取决于每个函数调用(很难找到)和Xss的堆栈大小。 如上所述,每个函数调用的默认总大小和大小(取决于内存页大小等)是平台特定的。 因此不同的行为。

    -Xss 4M调用java调用给我41 。 因此是相关性。

    链接地址: http://www.djcxy.com/p/80455.html

    上一篇: Why does this method print 4?

    下一篇: How to get full stack of StackOverflowError