计算方法调用堆栈大小以检查StackOverflowException

时间:2013-04-17 08:42:17

标签: java recursion stack callstack stack-size

今天早上我回答了一个与StackoverflowException相关的问题。该人询问何时发生Stackoverflow异常

请参阅此链接Simplest ways to cause stack overflow in C#, C++ and Java

所以我的问题是,有没有任何方法可以在程序中动态计算方法调用堆栈大小,然后在调用方法之前应用检查,该方法检查方法调用堆栈是否有空间容纳它或不防止StackOverflowException。

由于我是一名java人,我正在寻找java,但也在寻找与概念相关的解释而不受任何编程语言的束缚。

5 个答案:

答案 0 :(得分:9)

对于32位JVM,JVM可用的总内存大约为2-4GB,而对于64位JVM(大约4-16EB),这个方块的平方大小约为2-4GB。 JVM将其内存分成:

  1. 堆内存(通过JVM选项-Xms和-Xmx控制分配)

    • 构造的对象和数组实例
    • 静态类和数组数据(包括包含的对象/数组实例)
    • 线程实例(对象实例,运行时数据和元数据,包括线程对象监视器锁引用)
  2. 非堆内存

    • 聚合堆栈内存
      • 每线程堆栈内存(通过JVM选项-Xss控制每线程分配):方法调用帧,参数,返回值,本地声明的原语&对象的引用
    • 静态常量(原语)
    • 字符串实例池
    • java code:已加载的类和元数据
    • JVM内部使用内存(JVM代码和数据结构)
  3. 请参阅http://docs.oracle.com/javase/7/docs/api/java/lang/management/MemoryMXBean.htmlhttp://www.yourkit.com/docs/kb/sizes.jsp

      

    是否有任何方法可以在我们的程序中动态计算方法调用堆栈大小

    1. Java SE / Java EE中没有包含标准方法来获取每线程堆栈的实际内存使用情况。
    2. 有一些标准方法可以获得聚合非堆内存:MemoryMxBean.getNonHeapMemoryUsage()。参考此操作不允许您制定动态的代码内决策以避免StackOverflow异常
    3. 有一些标准方法可以在不占用内存的情况下获取调用堆栈:Thread.getStackTrace() ThreadMxBean.getThreadInfo()& ThreadInfo.getStackTrace()
    4. 我建议您不要在问题中做出建议,因为:

      • 如果没有一些复杂的特定于JVM的API来监视动态线程堆栈内存的使用,你就无法做到 - 你会在哪里找到这样的API?
      • 每个线程堆栈通常占用相对于整个JVM的少量内存,因此通常很容易分配足以满足您的算法(例如,对于Windows 64位,默认为128KB堆栈大小JVM虽然可能已为整个JVM预算了2GB的内存)
      • 它的功率非常有限:如果你的逻辑实际上需要调用一个方法,但由于内存不足你就不能,那么你的程序就会被打破。 StackOverflow例外实际上是最佳回复。
      • 您要做的是反设计反模式
        “正确”的方法是指定程序要求,指定所需的运行时环境(包括最小/需要的内存!),并相应地设计程序以获得最佳性能和内存使用。

        反模式是在设计和开发过程中不要适当考虑这些事情,只是想象一些运行时内省魔术可以涵盖这一点。可能存在一些(罕见的)高性能要求的应用程序,这些应用程序需要在运行时彻底重新排列算法以与发现的资源完全匹配 - 但这是复杂,丑陋和昂贵。

        即便如此,从“-Xss”参数驱动宏级别的动态算法更改可能会更好,而不是代码中某个位置的精确堆栈内存消耗的微观级别。

答案 1 :(得分:5)

我希望我猜你真正在问什么。起初我以为你在问你的电话会有多少电话。换句话说,根据您当前的方法情况,我认为您想知道触发此异常的可能性有多大。然后我决定你真的想知道你需要多少叠加深度。在这种情况下,这里有另一个堆栈溢出问题似乎解决了这个问题。 What is the maximum depth of the java call stack?

这告诉您如何将其设置为java命令行参数(对java而不是程序)。

无论哪种方式,我都想指出,当我进行无休止的递归时,堆栈溢出主要发生在我身上。我写过自己的方法(当然是错误的),并且当问题得到解决时意味着停止,但不知何时终止条件从未到达。这会将方法调用反复放到堆栈中,直到超出最大值。不是我的想法。

我希望有所帮助。

答案 2 :(得分:3)

据我所知,Java中的堆栈限制非常抽象,不适用于测量。事实上,我怀疑基于内存等几个因素,堆栈大小会因机器而异。

除了无限循环/递归之外,我从未得到过抛出堆栈溢出异常的程序。我正在试图弄清楚如何在没有无限循环的情况下抛出堆栈溢出异常。如果你的程序调用了很多方法,那么很可能同时创建对象,并且你更有可能收到OutOfMemory错误,而不是没有无限循环的堆栈溢出异常。

事实上,堆栈限制的重点是什么,可能会限制你正常运作的能力? Java有内存限制来照顾你过度使用资源。堆栈溢出的目的是捕获已经运行并且需要被捕获的循环/递归。

我想说的是:如果堆栈溢出异常困扰你的单元测试,你应该检查一些失控行为的循环/递归函数。调用堆栈非常非常长,我怀疑你是否已经自然地达到了它。

答案 3 :(得分:1)

我认为您可以使用StackTrace来获取方法调用堆栈大小,如下所示

 StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();

答案 4 :(得分:1)

好吧,你可以使用C和Microsoft C ++编译器一样的东西: 在每个开始和结束函数上自动调用的特定函数(我不记得名称)。

此外,您可以通过递增来计算调用次数和子次数,并在启动函数之后和结束函数之前递减全局计数器。

例如,使用Microsoft .NET,您可以插入一些函数调用以在每次调用时递增和递减全局计数器。它是JIT设计的。

您还可以使用nosql数据库来存储您的电话。

此外,还有另一件事:使用自动跟踪您的通话的日志系统。

此外,当您的调用堆栈已满时,有时它是由递归函数引起的。使用几行代码和一个对象,您可以在每次调用时在每个函数上存储一些传播。 该解决方案还可用于在任何功能中检测特殊事物:“谁在呼唤我?”

此外,由于Java是生成的字节码,因此您可以检测函数调用的字节码,并在另一个函数调用之前插入,并在另一个函数调用之后插入以添加自定义堆栈。