关于 StackOverflowError 和 OutOfMemoryError 的疑惑

2020-08-31 17:08:28 +08:00
 JasonLaw

在“深入理解 Java 虚拟机(第 2 版)- 2.4.2 虚拟机栈和本地方法栈溢出”中,它说:

关于虚拟机栈和本地方法栈,在 Java 虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。

关于“如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常”,我查阅了What is the maximum depth of the java call stack? - Stack Overflow,其实根本没有"maximum depth of the java call stack"这个东西。在memory leaks - What is a StackOverflowError? - Stack Overflow中,Varun 的回答说“If there is no space for a new stack frame then, the StackOverflowError is thrown by the Java Virtual Machine (JVM).”。

关于“如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常”,在java - JVM option -Xss - What does it do exactly? - Stack Overflow中,Adam Adamaszek 的回答说“OutOfMemoryError: unable to create new native thread (too many threads, each thread has a large stack)”。

我的疑问

  1. 书中所说的内容是不是不太正确?或者说不太容易理解?

  2. 关于 StackOverflowError 和 OutOfMemoryError 的定义,"StackOverflowError: the stack size is greater than the limit, OutOfMemoryError: unable to create new native thread"是正确的。对吗?

2357 次点击
所在节点    Java
9 条回复
hotpot6147
2020-08-31 17:25:02 +08:00
肯定是正确的啊

举个栗子:
假设你的 jvm 限制每个栈最大为 1MB,jvm 堆内存为 300MB

如果你的程序里有很多很多线程,每个线程栈都在 1MB 以内,但是它们的合计大小超过了 300MB,这就会触发 OutOfMemoryError

如果你的程序里线程的合计大小小于 300MB,但是有一个线程使用的栈超过了 1MB (比如写了个嵌套层级很深的递归方法)那么就会触发 StackOverflowError
vincenttone
2020-08-31 20:13:25 +08:00
JVM 手册里有写:
The following exceptional conditions are associated with Java Virtual Machine stacks:
• If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
• If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.

一个是有栈但是不够用导致的,另一个就是申请栈内存(或者动态扩展)失败导致的。相当于一个触发来自栈管理器、一个触发来自内存管理器——所以异常的名字一个是关于栈的,另一个是关于内存的。
JasonLaw
2020-08-31 20:31:58 +08:00
vincenttone 所说的内容来源于 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html 中的 “2.5.2. Java Virtual Machine Stacks”
CRVV
2020-08-31 21:17:16 +08:00
从回复来看,这个书只是翻译了 Java 文档而已。

然后这个文档是 Java Virtual Machine Specifications,它是定义 JVM 的文档,而不是描述 Oracle JVM 工作方式的文档。
这个文档当然没有写错,但它确实写成了一个不太好懂的方式。当然了这个文档有 600 多页,也不是给初学者看的。

把有疑问的内容都粘过来,是这样的

This specification permits Java Virtual Machine stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen independently when that stack is created.

A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of Java Virtual Machine stacks, as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.

The following exceptional conditions are associated with Java Virtual Machine stacks:

If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.

If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.


所以,
这里有两种情况,分别是 fixed sized 的栈和可以 dynamically expanding 的栈。
maximum depth of the java call stack
对 fixed 的栈来说,那显然是它的初始大小,因为它不能变。
对 dynamical 的栈来说,那显然是它的 maximum size,在第二段里提到了。

后面那个 OutOfMemoryError 的前提是 dynamical 的栈,所以就是 expand stack 的时候,内存不够了报的错。

Oracle JVM 的栈是 fixed sized,-Xss 是在设定栈的大小,也就是第二段里说的 initial size of Java Virtual Machine stacks,它和这个 OutOfMemoryError 毫无关系。
lff0305
2020-09-01 10:41:30 +08:00
jvm 通过-xss 指定每个线程使用的栈的大小(类似的 createThread api 同样可以指定栈的大小).记得默认 java 栈大小是 512kb,C++是 2MB. 栈的大小决定了函数调用的深度(因为 Java 不像 c++。c++要在栈上传递值或者引用。Java 只需要传递引用。所以 jvm 选择了一个较小的栈).

上文说的 outofmemory error 是针对一个虚拟机能创建的最多的线程数。因为栈是在 heap 上分配的,自然最大的线程数就约等于 xmx / xss 。
创建了这么多线程,再创建新的线程就会出现 outofmemory error 因为没有剩余的 heap 保留来做线程栈
JasonLaw
2020-09-01 10:54:19 +08:00
说句不好听的话,我感觉回复总是出现一些答非所问,还是希望大家认真阅读题目,然后再回答。如果是我自己没有描述清楚的话,可以直接在回复里面沟通。
251
2020-09-01 20:41:27 +08:00
@CRVV 说的对,是你自己没看懂而已。
If there is no space for a new stack frame then, the StackOverflowError is thrown by the Java Virtual Machine (JVM).
这种说话和"请求的栈深度大于虚拟机所允许的最大深度" 是一个意思而已。


OutOfMemoryError: unable to create new native thread (too many threads, each thread has a large stack)”。
这个只是 OutOfMemoryError 的其中一种情况。
JasonLaw
2020-09-01 21:11:08 +08:00
@251 #7 我没有说 CRVV 说的有问题,很多回复说的都是对的。但是对的不代表回答了我的问题,我没有说它们的内容是错的,我说的是很多回复都是答非所问,基本并没有正面回答我的两个疑问,而我的疑问更多的是关于书本所说内容的正确性。

如果你觉得“If there is no space for a new stack frame then, the StackOverflowError is thrown by the Java Virtual Machine (JVM).”跟“请求的栈深度大于虚拟机所允许的最大深度”相等的话,那我无话可说。
251
2020-09-01 22:21:00 +08:00
CRVV 已经正面回答你了,只是你自己没看懂,觉得答非所闻。

相不相等要看本质。"If there is no space for a new stack frame then, the StackOverflowError is thrown by the Java Virtual Machine (JVM)" 本质是超过了设定的值,“请求的栈深度大于虚拟机所允许的最大深度”的本质一样也是超过了设定的值. 这个值就是虚拟机设置的 fixed sized ;

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/702881

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX