Java (JVM) 如何为每个线程分配堆栈

2022-08-31 15:15:06

Java 应用程序启动时,所有线程都有一个堆。每个线程都有自己的堆栈。

当Java应用程序启动时,我们使用JVM选项来控制堆的大小并控制堆栈的大小。-Xms-Xmx-Xss

我的理解是,正在创建的堆成为JVM的“托管”内存,并且所有正在创建的对象都放置在那里。

但是堆栈创建是如何工作的呢?Java 在创建每个线程时是否为其创建堆栈?如果是这样,堆栈在内存中的确切位置?它肯定不在“托管”堆中。

JVM 是从本机内存创建堆栈,还是为堆栈预先分配了一部分托管内存区域?如果是这样,JVM如何知道线程将如何创建?


答案 1

关于线程堆栈,Java 规范告诉我们一些事情。其中包括:

  • 每个 Java 虚拟机线程都有一个私有 Java 虚拟机堆栈,与该线程同时创建。

  • 由于 Java 虚拟机堆栈除了推送和弹出帧外,从不直接操作,因此可以堆分配帧。Java 虚拟机堆栈的内存不需要是连续的。

  • 规范允许 Java 虚拟机堆栈具有固定大小,或者根据计算的需要动态扩展和收缩。

现在,如果我们专注于JVM实现,如HotSpot,我们可以得到更多的信息。以下是我从不同来源收集的一些事实:

  • 线程的 HotSpot 中的最小堆栈大小似乎是固定的。这就是上述选项的用途。(来源)-Xss

在 Java SE 6 中,Sparc 上的缺省值在 32 位 VM 中为 512k,在 64 位 VM 中为 1024k。...您可以通过使用 -Xss 选项运行来减小堆栈大小。...64k 是每个线程允许的最小堆栈空间量。

  • JRockit 分配独立于堆栈所在的堆的内存。(来源)

请注意,JVM 使用的内存不仅仅是堆。例如,Java 方法、线程堆栈和本机句柄分配在与堆以及 JVM 内部数据结构分开的内存中。

  • 在 HotSpot 中,Java 线程和本机操作系统线程之间存在直接映射。(来源).

  • 但是 HotSpot 中的 Java 线程堆栈是软件托管的,它不是操作系统原生线程堆栈。(来源)

它使用单独的软件堆栈来传递 Java 参数,而本机 C 堆栈由 VM 本身使用。许多 JVM 内部变量(如程序计数器或 Java 线程的堆栈指针)存储在 C 变量中,不能保证这些变量始终保存在硬件寄存器中。管理这些软件解释器结构消耗了总执行时间的相当大一部分。

  • JVM还利用相同的Java线程堆栈进行本机方法和JVM运行时调用(例如.class加载)。(来源).

  • 有趣的是,作为性能优化,即使分配的对象有时也可能位于堆栈上,而不是堆上。(来源)

JVM可以使用一种称为转义分析的技术,通过该技术,它们可以判断某些对象在其整个生命周期中仍然局限于单个线程,并且该生命周期受给定堆栈帧的生存期的限制。此类对象可以安全地分配在堆栈上而不是堆上。

因为一张图片胜过千言万语,所以这是詹姆斯·布鲁姆(James Bloom)的一张图片 Java memory


现在回答您的一些问题:

JVM如何知道线程将如何创建?

事实并非如此。可以通过创建可变数量的线程来轻松证明矛盾。它确实对每个线程的最大线程数和堆栈大小进行了一些假设。这就是为什么如果分配了太多线程,内存可能会耗尽(不是指堆内存!

Java 在创建每个线程时是否为其创建堆栈?

如前所述,每个 Java 虚拟机线程都有一个私有 Java 虚拟机堆栈,与该线程同时创建。

如果是这样,堆栈在内存中的确切位置?它肯定不在“托管”堆中。

如上所述,从技术上讲,Java规范允许堆栈内存存储在堆上。但至少JRockit JVM使用内存的不同部分。

JVM 是从本机内存创建堆栈,还是为堆栈预先分配了一部分托管内存区域?

该堆栈由 JVM 管理,因为 Java 规范规定了它必须如何运行:Java 虚拟机堆栈存储帧 (§2.6)。Java 虚拟机堆栈类似于传统语言的堆栈。一个例外是用于方法的本机方法堆栈。在规范中再次对此进行更多介绍。native


答案 2

JVM 使用的内存不仅仅是堆。例如,Java 方法、线程堆栈和本机句柄分配在与堆以及 JVM 内部数据结构分开的内存中。

进一步阅读

因此,要回答您的问题:

Java 在创建每个线程时是否为其创建堆栈?

是的。

如果是这样,堆栈在内存中的确切位置?

在 JVM 中分配内存,但不在堆上。

如果是这样,JVM如何知道线程将如何创建?

事实并非如此。

您可以根据需要创建任意数量的内存,直到最大化 JVM 内存并获得

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

编辑:

以上所有内容都是指Jrockit JVM,尽管我发现很难相信其他JVM在此类基本问题上会有所不同。