Java堆栈和堆内存管理

时间:2016-12-13 11:34:58

标签: java memory memory-management heap-memory stack-memory

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

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


在上图中,堆栈内存中的内存 obj s 实际上是对其&#34; <的引用strong>实际对象&#34;它们被放置在堆内存中。
以下是我想到的一系列问题:

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

3 个答案:

答案 0 :(得分:19)

  

存储方法在哪里?

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

  

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

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

  

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

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

  

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

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

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

答案 1 :(得分:7)

第一件事 :我假设您在阅读this文章之后提出了问题(因为在那里我看到了一个非常相似的图表作为你的)所以我不会引用或突出那里提到的任何要点,并试图用那些在那篇文章中不那么明显的要点回答你的问题。

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

JVM内存区域

首先,我将这两个图表描绘为JVM内存区域:

Source of diagram

enter image description here

Source of diagram

enter image description here

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

  • 堆内存
    • 年轻一代
      • 伊甸园空间
      • 幸存者空间
    • 老一辈
      • Tenured Generation
  • NonHeap内存
    • 永久世代
    • 代码缓存(我认为包含&#34;仅#34;由HotSpot Java VM

堆内存

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

年轻一代

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

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

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

老一代

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

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

非堆内存

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

永久世代

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

代码缓存

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

具体回答OP的问题

  

存储方法在哪里?

非堆内存 - &gt;永久生成

  

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

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

JVM只加载一次类,当它加载类时,空间被分配在&#34;永久生成&#34;该类由JVM加载时只发生一次。

  

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

myMethod创建的堆栈帧将从堆栈内存中删除,因此为局部变量创建的所有内存都将被清除,但这并不意味着JVM将清理在&#34中分配的内存;永久性一代&#34;对于您在myMethod

中创建的那些对象
  

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

特别是在讨论String类时,JVM会为String中的{{1}}分配空间,永久生成&#34;太早了,当JVM启动时,无论你是否初始化你的String变量,它都不会引起永久性生成&#34;透视图。

谈到其他用户定义的类,JVM将加载类并在&#34; Permanent Generation&#34;中分配内存。一旦你定义了类,即使你没有创建类的对象,内存也会在&#34;永久生成&#34; (非堆区)并且当您创建类的对象时,内存将在&#34; Eden Space&#34;中分配。 (堆区)。

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

答案 2 :(得分:2)

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

  

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

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

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