Java中的静态分配 - 堆、栈和永久代

126

我最近一直在阅读关于Java中内存分配方案的内容,但是从各种来源中阅读到的信息让我产生了很多疑问。我已经整理好了我的概念,并请求您查看所有问题并对其进行评论。我知道内存分配是JVM特定的,因此我必须事先说一下,我的问题是针对Sun的。

  1. 类(由类加载器加载)进入堆的一个特殊区域:永久代。
  2. 与类相关的所有信息,例如类的名称、与类相关的对象数组、JVM使用的内部对象(如java/lang/Object)和优化信息都放在永久代区域。
  3. 所有静态成员变量再次保留在永久代区域中。
  4. 对象进入不同的堆:年轻代。
  5. 每个类只有一个方法的副本,无论该方法是静态的还是非静态的。该副本被放置在永久代区域。 对于非静态方法,所有参数和局部变量都进入堆栈——每当调用该方法时,我们会得到与之关联的新堆栈帧。 我不确定静态方法的局部变量存储在哪里。它们是在永久代的堆上存储吗?还是只存储它们的引用在永久代区域,而实际副本存储在其他地方(哪里?)
  6. 我也不确定方法的返回类型存储在哪里。
  7. 如果年轻代中的对象需要使用永久代中的静态成员,则会给它们一个静态成员的引用,并为其分配足够的内存空间来存储方法的返回类型等。

感谢您阅读这篇文章!

1个回答

163

首先,现在应该很清楚,很少有人能够从第一手的知识来确认这些答案。极少有人曾经在最近的HotSpot JVM上工作过或者深入研究过它们以真正了解。大多数人(包括我自己)都是根据他们在其他地方看到的东西或者推断出来的东西来回答的。通常这里或者各种文章和网页中所写的内容是基于其他可能或可能不是决定性的来源。通常情况下,它被简化、不准确或者只是错了。

如果您想要确切的答案确认,您真的需要下载OpenJDK源代码...通过阅读和理解源代码进行自己的研究。在SO上提问,或者在随机的网页文章中搜索并不是一个可靠的学术研究技巧。

话虽如此...

...我的问题是Sun公司特定的。

在提问时,Sun Microsystems已经不存在了。因此,这个问题是Oracle特定的。据我所知,所有当前(非研究)的第三方JVM实现都是直接从OpenJDK发布版移植或者从另一个Sun/Oracle发布版继承而来的。

下面的答案适用于Oracle Hotspot和OpenJDK发布版,可能也适用于其他大多数实现...包括GraalVM。

1)类(由类加载器加载)进入堆的特殊区域:永久代。

在Java 8之前,是的。

从Java 8开始,永久代空间已经被Metaspace所取代。加载和JIT编译的类现在进入那里。PermGen不再存在。

2)与类相关的所有信息,如类名、与类关联的对象数组、JVM使用的内部对象(如java/lang/Object)和优化信息都放在永久代区域中。
3)所有静态成员变量再次保存在永久代区域中。
4)对象放在不同的堆中:年轻代。
5)每个类只有一个方法的副本,无论该方法是静态还是非静态。该副本放在永久代区域中。对于非静态方法,所有参数和局部变量都进入堆栈,并且每当具体调用该方法时,我们都会获得与之关联的新堆栈帧。

我不确定静态方法的本地变量存储在哪里。它们是存储在永久代的堆上吗?还是它们的引用只存储在永久代区域,而实际副本则存储在其他地方(在哪里)?

不是的。它们像非静态方法中的本地变量一样存储在堆栈上。

6)我也不确定方法的返回类型存储在哪里。

如果您指的是由(非void)方法调用返回的值,则它将返回在堆栈或机器寄存器中。如果它在堆栈上返回,则需要1或两个字,具体取决于返回类型。

7)如果对象(在年轻代)需要使用静态成员(在永久代中),它们会获得对静态成员的引用,并且会获得足够的内存空间来存储方法的返回类型等。

那是不准确的(或者至少,您没有清楚地表达自己)。

如果某个方法访问静态成员变量,则获取的是原始值或对象引用。这可以分配给(现有的)局部变量或参数,分配给(现有的)静态或非静态成员,分配给先前分配的数组元素(现有的)或仅仅使用并丢弃。

  • 在任何情况下,都不需要分配新的存储空间来容纳引用或原始值。

  • 通常,一个字的内存就足以存储对象或数组引用,原始值通常占用一到两个字,具体取决于硬件架构。

  • 在任何情况下,调用者都不需要分配空间来容纳某个方法返回的对象/数组。在Java中,对象和数组总是使用传值语义返回...但返回的值是一个对象或数组引用。


更多信息,请参考以下资源:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接