11

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

请参阅此链接在 C#、C++ 和 Java 中导致堆栈溢出的最简单方法

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

因为我是一个 java 人,所以我在寻找 java,但也在寻找与这个概念相关的解释,不受任何编程语言的限制。

4

5 回答 5

11

对于 32 位 JVM,JVM 可用的总内存约为 2-4GB,对于 64 位 JVM(约 4-16EB)。JVM 将其内存拆分为:

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

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

    • 聚合堆栈内存
      • 每线程堆栈内存(通过 JVM 选项 -Xss 控制的每线程分配):方法调用帧、参数、返回值、本地声明的原语和对对象的引用
    • 静态常量(原语)
    • 字符串实例池
    • java代码:加载的类和元数据
    • JVM 内部使用内存(JVM 代码和数据结构)

请参阅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()。引用 this 不允许您做出动态的代码内决策以避免StackOverflow异常
  3. 有一些标准方法可以在不占用内存的情况下获取调用堆栈: Thread.getStackTrace() ThreadMxBean.getThreadInfo() & ThreadInfo.getStackTrace()

我建议您不要执行问题中的建议,因为:

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

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

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

于 2013-05-20T03:54:12.260 回答
5

我希望我猜到你真正在问什么。起初我以为你在问你的电话要打多少电话。换句话说,我认为您想知道根据您当前的方法环境触发此异常的可能性有多大。然后我决定你真的想知道你必须玩多少筹码深度。在这种情况下,这里还有另一个堆栈溢出问题似乎可以解决这个问题。 java调用栈的最大深度是多少?

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

无论哪种方式,我想指出堆栈溢出主要发生在我有无休止的递归时。我已经编写了调用自己的方法(当然是错误的),并且本应在问题解决时停止,但不知何故从未达到终止条件。这会一遍又一遍地将方法调用放到堆栈上,直到超过最大值。不是我想的那样。

我希望这会有所帮助。

于 2013-04-26T13:10:52.843 回答
3

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

除了无限循环/递归之外,我从来没有让程序抛出堆栈溢出异常。我正在挠头,试图弄清楚如何在没有无限循环的情况下抛出堆栈溢出异常。如果你的程序调用了这么多方法,那么它很可能同时创建对象,与没有无限循环的堆栈溢出异常相比,你更有可能收到 OutOfMemory 错误。

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

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

于 2013-05-20T04:04:28.180 回答
1

我认为您可以使用StackTrace 以下方法获取方法调用堆栈大小

 StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
于 2013-04-17T08:45:20.437 回答
1

好吧,您可以使用类似于 C 中存在的东西和 Microsoft C++ 编译器:一个特定的函数(我不记得名字),它在每个开始和结束函数上自动调用。

此外,您可以通过在 start 函数之后和 end 函数之前递增和递减全局计数器来计算调用和子调用的数量。

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

您还可以使用 nosql 数据库来存储您的呼叫。

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

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

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

于 2013-05-21T05:56:23.060 回答