Java栈和堆内存管理

Posted

技术标签:

【中文标题】Java栈和堆内存管理【英文标题】:Java stack and heap memory management 【发布时间】:2017-04-28 10:57:46 【问题描述】:

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

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

    


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

上图中,栈内存中的memoryobjs其实是对它们“actual”的引用对象”放置在堆内存中。 以下是我想到的一组问题:

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

【问题讨论】:

Java 的优点在于它隐藏所有内存管理,让垃圾收集器控制。为什么你需要去调查它? 只是出于好奇 @byxor 据我所知,有两个直接原因: 1. 当出现 SOF 或 OOM 时,您必须了解它们才能修复错误; 2. 在性能调优中试图更好地管理内存时,你必须了解它们。 【参考方案1】:

s的方法存放在哪里?

它们存储在 String 类对象中;它是在程序中首次引用 String 时由 ClassLoader 对象加载的对象。当我最后读到这个时,所有 JVM 的实现都不会在加载类对象后释放它的内存。它在堆上。

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

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

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

没有。 Java 通常不会“立即释放内存”存储在堆上的东西。它会使事情运行得太慢。它仅在垃圾收集器运行时释放内存,并且只有在其运行垃圾收集器的算法确定时间到时才会释放内存。

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

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

我鼓励您阅读有关 JVM 和 ClassLoaders 等的信息;通过阅读有关其工作原理的说明而不是通过您可以想到的示例来探究它,您将获得更多。

【讨论】:

为什么MemoryClass的第二个对象的方法没有像s一样存储在堆上? 它们存储在堆中,但只有一个副本。大多数关于为对象分配内存的讨论并没有区分,但是当我们谈论为对象的实例分配内存时,可以理解系统只是为数据分配额外的内存。无需为方法的相同副本分配内存。我不记得了,但可能这些方法与该类的类对象一起存储,因为只需要它们的一个副本。 (不同的类加载器将加载不同的类对象,但我在这里无法解释)。【参考方案2】:

第一件事:我假设您的问题是在阅读 this 文章后提出的(因为我看到了与您的非常相似的图表)所以我不会引用或强调那里提到的任何观点,并会尝试用在那篇文章中不太明显的观点来回答你的问题。

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


JVM 内存区域

让我先放两张描绘 JVM 内存区域的图:

Source of diagram

Source of diagram

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

堆内存 年轻一代 伊甸空间 幸存者空间 老一代 老一代 非堆内存 永久世代 代码缓存(我认为“仅”包含在 HotSpot Java VM 中

堆内存

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

年轻一代

年轻一代是创建所有新对象的地方。当年轻代被填满时,执行垃圾回收。这种垃圾回收称为 Minor GC。年轻代分为以下两部分

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

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

老一代

Old Generation 内存包含经过多轮 Minor GC 后存活并存活的对象。通常垃圾收集在老一代内存满时执行。 Old Generation Garbage Collection 称为 Major GC,通常需要更长的时间。老年代包含以下部分:

Tenured space:包含在幸存者空间中已存在一段时间的对象的池。

非堆内存

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

永久代

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

代码缓存

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


专门回答OP的问题

s的方法存放在哪里?

非堆内存 --> 永久代

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

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

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

JVM 是否会尽快释放分配给 myMethod 的内存 执行完成,如果是,它将如何处理这种情况 问题 2 中提到的(仅适用于 JVM 分配内存的情况) 多次使用相同的方法)。

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

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

具体来说String类,JVM会在“永久生成”中为String分配空间太早了,当JVM启动时,无论你是否初始化你的String变量,都无关紧要“永久一代”的观点。

说到其他用户自定义的类,只要你定义了类,JVM就会在“Permanent Generation”中加载该类并分配内存,同样即使你没有创建该类的对象,内存也是在“永久代”(非堆区域),当您创建类的对象时,内存将分配到“伊甸园空间”(堆区域)。


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

http://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.html http://www.journaldev.com/2856/java-jvm-memory-model-memory-management-in-java https://blogs.oracle.com/jonthecollector/entry/presenting_the_permanent_generation Java heap terminology: young, old and permanent generations?

【讨论】:

@Adit:赏金通常是在已经提供的答案不够“详细”且不够充分的情况下放置的,所以我想知道你为什么在你的这个问题上悬赏,因为答案是“arcy”已经在“12 月 13 日”发放,你最近放了赏金?【参考方案3】:

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

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

基本上,虽然具有字段和方法信息的类数据确实存储在永久代(从 JDK-8 开始的元空间)中,但重要的是要注意它是 java 中的对象在堆上为其分配数据的 .lang.String 类(例如 char[] 保存该字符串的所有字符信息)。

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

【讨论】:

以上是关于Java栈和堆内存管理的主要内容,如果未能解决你的问题,请参考以下文章

Java中栈和堆的区别

Swift 栈和堆

JVM 运行时数据区:程序计数器Java 虚拟机栈和本地方法栈,方法区和堆

java内存模型,内存区域

JAVA中的栈和堆

Java中的栈和堆