Java堆栈和堆内存管理

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

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!"; } } 

现在,就我的理解而言,下图描述了内存分配的发生方式:
基本运行时内存分配

在上图中,堆栈存储器中的内存objs实际上是对它们放置在堆内存中的“ 实际对象 ”的引用。
以下是我想到的一系列问题:

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

存储的方法在哪里?

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

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

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

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

没有.Java一般不会“立即释放存储在堆上的东西”。 这会使事情运行得太慢。 它只在垃圾收集器运行时释放内存,并且只有当它运行垃圾收集器的算法决定它是时候时它才会这样做。

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

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

我鼓励你阅读有关JVM和ClassLoaders等内容; 通过阅读有关它如何工作的解释,你可以获得更多,而不是用你可以想到的例子来嘲笑它。

首先要做的事情是 :我假设你的问题在阅读完这篇文章后出现( 因为在那里我看到的图表和你的图表非常相似 )所以我不会引用或突出显示那里提到的任何一点,并会尝试回答对于你的问题,在那篇文章中并不那么明显。

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


JVM内存区域

让我首先介绍这两个描绘JVM内存区域的图:

图表来源

在此处输入图像描述

图表来源

在此处输入图像描述

现在,从上面的图表中可以清楚地看到JVM内存的树形结构,我将尝试对它进行说明@Adit:请注意,与您有关的区域是PermGen Space或非堆内存的永久生成空间 ) 。

  • 堆内存
    • 年轻一代
      • 伊甸园空间
      • 幸存者空间
    • 老一代
      • 终身代
  • NonHeap内存
    • 永久代
    • 代码缓存( 我认为HotSpot Java VM只包含“ )”

堆内存

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

年轻一代

年轻一代是创建所有新对象的地方。 当年轻一代被填满时,进行垃圾收集。 此垃圾收集称为Minor GC。 Young Generation分为以下两部分

Eden空间:最初为大多数对象分配内存的池。

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

老一代

旧生成内存包含多轮次要GC后长寿和存活的对象。 通常垃圾收集在Old Generation内存中完成时执行。 旧一代垃圾收集称为主要GC,通常需要更长的时间。 老一代包含以下部分:

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

非堆内存

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

永久世代

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

代码缓存

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


特别回答OP的问题

存储的方法在哪里?

非堆内存 – >永久生成

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

堆栈内存只包含局部变量,因此新的MemoryClass ORV(对象引用变量)仍将在myMethod堆栈帧中创建,但JVM不会在“Permanent Generation”中再次加载MemoryClass所有方法,元数据等。

JVM只加载一次类,当它加载类时,则在该类的“永久生成”上分配空间,并且在JVM加载类时只发生一次。

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

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

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

特别是在讨论String类时,JVM会过早地为“永久生成”方式为String分配空间,而JVM启动时无论是否初始化String变量,从“永久生成”角度来看都无关紧要。

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


以上信息和进一步阅读的来源:

由于arsy接受的答案和hagrawal的答案很清楚,只想详细说明第四个问题:

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

基本上,尽管类数据(具有字段和方法信息)存储在永久生成(从JDK-8开始的元空间)中,但重要的是要注意它在java.lang中的对象。字符串类(例如,包含该字符串的所有字符信息的char []),用于在堆上分配数据。

在创建新的字符串对象之前不会发生这种情况 – 使用’new’关键字或创建新的字符串文字(例如:“helloworld”)。