Java 堆栈和堆内存管理JVM 内存区域具体回答OP的问题以上信息来源及延伸阅读:

我想知道以下程序中内存是如何分配的:

public class MemoryClass {

    public static void main(final String[] args) {
        int i = 0;
        MemoryClass memoryClass = new MemoryClass();
        memoryClass.myMethod(memoryClass);
    }

    private void myMethod(final Object obj) {
        int i = 1;
        String s = "HelloWorld!";

    }

}

现在,就我的理解而言,下图描述了内存分配是如何发生的:
Basic runtime memory allocation


在上图中,堆栈内存中的内存 objs 实际上是对放置在堆内存中的“实际对象”的引用。
以下是我脑海中浮现的一系列问题:

  1. s 的方法存储在哪里?
  2. 如果我创建了另一个 inside 对象,JVM 是否会在堆栈内存中再次为相同的方法分配内存?MemoryClassmyMethod
  3. JVM是否会在执行完成后立即释放分配给它的内存,如果是这样,它将如何管理问题2中提到的情况(仅适用于JVM多次分配给同一方法的内存)。myMethod
  4. 如果我只是声明了s而没有初始化它,JVM是否仍然会将内存分配给类的所有方法,如果是这样,为什么?java.lang.String

答案 1

s 的方法存储在哪里?

它们存储在 String 类对象中;它是在程序中首次引用 String 时由 ClassLoader 对象加载的对象。当我读到这最后一篇文章时,所有存在的JVM实现在加载类对象后从未为类对象释放内存。它在堆上。

如果我在 myMethod 中创建了另一个 MemoryClass 对象,JVM 是否会在堆栈内存中再次为相同的方法分配内存?

不可以,对象的方法和数据是分开保存的,特别是因为 JVM 永远不需要多个方法副本。

JVM是否会在执行完成后立即释放分配给myMethod的内存,如果是这样,它将如何管理问题2中提到的情况(仅适用于JVM多次分配给同一方法的内存)。

不。Java通常不会“立即释放存储在堆上的东西的内存”。这会让事情运行得太慢。它仅在垃圾回收器运行时释放内存,并且仅当其运行垃圾回收器的算法确定是时候时才释放内存。

如果我只是声明了s而没有初始化它,JVM是否仍然会将内存分配给java.lang.String类的所有方法,如果是这样,为什么?

我认为这取决于JVM的实现,也许还有编译器。如果声明一个变量但从不使用它,编译器很可能会(并且很常见)注意到它没有用处,并且不会将其放入类文件中。如果它不在类文件中,则永远不会引用它,因此不会加载它及其方法,等等。如果编译器无论如何都把它放进去,但它从未被引用过,那么ClassLoader就没有任何理由加载它,但我对它是否会被加载有点模糊。可能取决于 JVM 实现;它加载内容是因为存在类的变量还是仅在引用它们时才加载?有多少ClassLoader算法可以在4位数PIN的头上跳舞?

我鼓励您阅读有关JVM和ClassLoaders之类的信息;通过阅读有关其工作原理的解释,而不是用您可以想到的示例来戳它,您将获得更多。


答案 2

首先:我假设你的问题在阅读本文后出来(因为在那边我看到了一个和你非常相似的图表),所以我不会引用或强调那里提到的任何观点,并会尝试用在那篇文章中不那么明显的点来回答你的问题。

阅读您的所有问题,我的印象是,您很清楚如何在堆栈和堆中分配内存,但对类的元数据有疑问,即在内存中的位置,类的方法将被存储以及如何回收。所以,首先让我试着解释一下JVM内存区域:


JVM 内存区域

让我首先放置这两个描述JVM内存区域的图表:

图表来源

enter image description here

图表来源

enter image description here

现在,从下面的上图中可以清楚地看出JVM内存的树结构,我将尝试阐明相同的结构(@Adit:请注意,您关注的区域是PermGen空间或非堆内存的永久生成空间)。

  • 堆内存
    • 年轻一代
      • 伊甸园空间
      • 幸存者空间
    • 老一辈
      • 终身制一代
  • 非剧组记忆
    • 永久生成
    • 代码缓存(我认为“仅”包含 HotSpot Java VM)

堆内存

堆内存是运行时数据区域,Java VM 从中为所有类实例和数组分配内存。堆的大小可以是固定的,也可以是可变的。垃圾回收器是一种自动内存管理系统,用于回收对象的堆内存。

年轻一代

年轻一代是创造所有新对象的地方。当年轻一代被填满时,进行垃圾收集。此垃圾回收称为次要 GC。年轻一代分为以下2部分

伊甸园空间:最初从中为大多数对象分配内存的池。

幸存者空间:包含已在伊甸园空间的垃圾回收中幸存下来的对象的池。

老一辈

旧一代记忆包含长时间存在并在多次小GC之后幸存下来的物体。通常,垃圾回收在旧一代内存中执行,当它已满时。旧一代垃圾回收称为主要GC,通常需要更长的时间。老一代包含以下部分:

终身空间:包含幸存者空间中已存在一段时间的物体的池。

非堆内存

非堆内存包括在 Java VM 的内部处理或优化所需的所有线程和内存之间共享的方法区域。它存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码。方法区域在逻辑上是堆的一部分,但根据实现的不同,Java VM 可能不会对其进行垃圾回收或压缩。与堆内存一样,方法区域可以是固定大小或可变大小。方法区域的内存不需要是连续的。

永久生成

包含虚拟机本身的所有反射数据(如类和方法对象)的池。对于使用类数据共享的 Java VM,这一代分为只读和读写区域。

代码缓存

HotSpot Java VM 还包括一个代码缓存,其中包含用于编译和存储本机代码的内存。


具体回答OP的问题

s 的方法存储在哪里?

非堆内存 --> 永久生成

如果我在 myMethod 中创建了另一个 MemoryClass 对象,JVM 是否会在堆栈内存中再次为相同的方法分配内存?

堆栈内存仅包含局部变量,因此您的 ORV(对象引用变量)的 new 仍将在 的堆栈帧中创建,但 JVM 不会在“永久生成”中再次加载所有方法、元数据等。MemoryClassmyMethodMemoryClass

JVM 只装入类一次,当它装入类时,会在“永久生成”上为该类分配空间,并且当类由 JVM 装入时,这种情况只发生一次。

JVM是否会在执行完成后立即释放分配给myMethod的内存,如果是这样,它将如何管理问题2中提到的情况(仅适用于JVM多次分配给同一方法的内存)。

创建的堆栈帧将从堆栈内存中删除,因此将为局部变量创建的所有内存都将被清除,但这并不意味着 JVM 将清理在“永久生成”中为您在其中创建的这些对象的类分配的内存myMethodmyMethod

如果我只是声明了s而没有初始化它,JVM是否仍然会将内存分配给java.lang.String类的所有方法,如果是这样,为什么?

具体来说,JVM会过早地在“永久生成”中分配空间,而JVM启动并且是否初始化String变量,从“永久生成”的角度来看并不重要。StringString

谈到其他用户定义的类,JVM 会在您定义类后立即加载该类并在“永久生成”中分配内存,同样,即使您不创建该类的对象,内存也会在“永久生成”(非堆区域)中分配,当您创建该类的对象时,内存将分配在“Eden Space”(堆区域)中。


以上信息来源及延伸阅读:


推荐