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

我最近阅读了很多关于java中的内存分配方案的内容,并且由于我从各种来源阅读,因此存在许多疑问。我已经收集了我的概念,我将要求遍历所有要点并对其进行评论。我开始知道内存分配是特定于JVM的,所以我必须事先说,我的问题是特定于Sun的。

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

谢谢你经历这个!


答案 1

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

如果你想明确确认你的答案,你真的需要下载OpenJDK源代码...并通过阅读和理解源代码进行自己的研究。在SO上提问,或随机浏览网络文章并不是一种健全的学术研究技术。

话虽如此...

...我的问题是太阳特有的。

在提出这个问题的时候,Sun Microsystems已经不复存在了。因此,这个问题是甲骨文特有的。AFAIK,所有当前(非研究)第三方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 个或两个单词,具体取决于返回类型。

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

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

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

  • 在任何情况下,都不需要分配新的存储来保存引用或基元值。

  • 通常,存储对象或数组引用所需的内存字就足够了,而基元值通常占用一个或两个字,具体取决于硬件体系结构。

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


有关详细信息,请参阅以下资源:


答案 2

推荐