java中的静态分配——堆、栈和永久代

Posted

技术标签:

【中文标题】java中的静态分配——堆、栈和永久代【英文标题】:static allocation in java - heap, stack and permanent generation 【发布时间】:2011-04-20 11:05:41 【问题描述】:

我最近阅读了很多关于 java 中的内存分配方案的内容,并且在我从各种来源阅读时有很多疑问。我已经收集了我的概念,我会要求仔细阅读所有要点并对其发表评论。我知道内存分配是特定于 JVM 的,所以我必须事先说明,我的问题是特定于 Sun 的。

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

感谢您完成这个!

【问题讨论】:

【参考方案1】:

首先,您现在应该清楚的是,很少有人可以根据第一手知识来确认这些答案。很少有人研究过最近的 HotSpot JVM,或者对它们进行了深入研究以真正了解。这里的大多数人(包括我自己)都是根据他们在别处看到的东西或他们推断的东西来回答的。通常,这里或各种文章和网页中所写的内容是基于其他来源,这些来源可能是确定的,也可能不是确定的。通常它是简化的、不准确的或完全错误的。

如果您想明确地确认您的答案,您确实需要下载 OpenJDK 源代码 ...并通过阅读和理解源代码自行研究。就 SO 提出问题或浏览随机的网络文章并不是一种可靠的学术研究技术。

话虽如此......

...我的问题是特定于 Sun 的。

在提出这个问题时,Sun Microsystems 已不复存在。因此,这个问题是 Oracle 特有的。 AFAIK,所有当前(非研究)的 3rd-party JVM 实现要么是 OpenJDK 版本的直接端口,要么是另一个 Sun/Oracle 版本的后代。

以下答案适用于 Oracle Hotspot 和 OpenJDK 版本,可能也适用于大多数其他版本......包括 GraalVM。

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

在 Java 8 之前,是的。

从 Java 8 开始,PermGen 空间已被 Metaspace 取代。加载和 JIT 编译的类现在可以放在那里。 PermGen 不再存在。

2) 与类相关的所有信息,如类名、与类关联的对象数组、JVM 使用的内部对象(如 java/lang/Object)和优化信息都进入永久生成区域。

或多或少,是的。我不确定你所说的其中一些是什么意思。我猜“JVM 使用的内部对象(如 java/lang/Object)”是指 JVM 内部的类描述符。

3) 所有的静态成员变量再次保存在永久代区域。

变量本身是的。这些变量(与所有 Java 变量一样)将保存原始值或对象引用。但是,虽然静态成员变量位于在 permgen 堆中分配的框架中,但这些变量引用的对象/数组可能会分配在 任何堆中。

4) 对象在不同的​​堆上:年轻代

不一定。大对象可以直接分配到老年代。

5) 每个类的每个方法只有一个副本,无论是静态方法还是非静态方法。该副本被放入永久代区域。

假设您指的是方法的代码,那么 AFAIK 是的。不过可能会稍微复杂一些。例如,该代码可能在 JVM 生命周期的不同时间以字节码和/或本机代码形式存在。

... 对于非静态方法,所有参数和局部变量都进入堆栈 - 每当对该方法进行具体调用时,我们都会获得一个与之关联的新堆栈帧。

是的。

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

没有。它们存储在堆栈中,就像非静态方法中的局部变量一样。

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

如果您的意思是由(非 void)方法调用返回的 ,那么它要么在堆栈中返回,要么在机器寄存器中返回。如果在栈上返回,则需要 1 或 2 个单词,具体取决于返回类型。

7) 如果对象(在年轻代)需要使用静态成员(在永久代),则为它们提供对静态成员的引用 && 为它们提供足够的内存空间来存储返回类型方法等

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

如果某个方法访问一个静态成员变量,它得到的要么是原始值,要么是对象引用。这可以分配给(现有)局部变量或参数,分配给(现有)静态或非静态成员,分配给先前分配的数组的(现有)元素,或者简单地使用和丢弃。

在任何情况下都不需要分配 存储来保存引用或原始值。

通常,存储对象或数组引用只需要一个内存字,而原始值通常占用一两个字,具体取决于硬件架构。

在任何情况下,调用者都不需要分配空间来保存某个方法返回的对象/数组。在 Java 中,始终使用按值传递语义返回对象和数组……但返回的值是对象或数组引用。


有关更多信息,请参阅以下资源:

Class metadata: a user guide What is the difference between PermGen and Metaspace? Java 8: From PermGen to Metaspace About G1 Garbage Collector, Permanent Generation and Metaspace

【讨论】:

以上是关于java中的静态分配——堆、栈和永久代的主要内容,如果未能解决你的问题,请参考以下文章

Java堆,新生代,老年代,永久代

Java永久代去哪儿了

Java中的新生代老年代永久代和各种GC

java虚拟机之内存分配

永久代退出舞台,元空间腾空出世

永久代退出舞台,元空间腾空出世