【问题标题】:Computing method call stack size for checking StackOverflowException计算方法调用堆栈大小以检查 StackOverflowException
【发布时间】:2013-04-09 22:39:17
【问题描述】:

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

查看此链接Simplest ways to cause stack overflow in C#, C++ and Java

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

由于我是 java 人,我正在寻找 java,但也在寻找与该概念相关的解释,不受任何编程语言的限制。

【问题讨论】:

  • 所有 Maven 开发者的答案?
  • @EJP 专家先生是那些随时准备分享他们的知识,随时准备帮助其他专家和经验丰富的人的人。所以这就是为什么我总是认为每个 stackoverflow 成员都是 maven 开发人员。
  • 这没有意义。有一个内部谬误。无论如何,最能回答您问题的人是 Java 开发人员。
  • @EJP 好的,对不起。谢谢你帮助我但我不知道如何删除带有 maven 词的语句。
  • 老兄,先生怎么了???

标签: java recursion stack callstack stack-size


【解决方案1】:

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

我建议您不要按照问题中的建议进行操作,因为:

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

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

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

【讨论】:

  • 先生,真是个解释,先生……太好了先生……您已经非常深入地解释了这个概念……谢谢先生。 +1 为你.....
  • 先生,我有一个问题“对于 32 位 JVM,JVM 大约为 2-4GB,对于 64 位 JVM(大约 4-16EB)”。先生,对于 64 位,它刚刚得到 4-16GB。
  • 没有。 (2-4 000 000 000) 平方字节(最大可寻址大小)。干杯
  • 谢谢先生,我认为这是这个问题的最佳答案。
【解决方案2】:

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

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

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

希望对你有帮助。

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

    • 不错...也感谢您的 +1。
    【解决方案4】:

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

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

    【讨论】:

    • @Sumit Singh 堆栈跟踪是程序执行过程中某个时间点的活动堆栈帧的一种视图。它没有描述它的大小。
    • StackTraceElement 中没有堆栈帧大小信息。 -1
    • @user207421但是数组有长度。 -1
    【解决方案5】:

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

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-26
      • 2017-04-01
      • 2011-02-23
      • 2010-12-17
      • 2014-12-07
      • 1970-01-01
      • 2020-11-05
      相关资源
      最近更新 更多