Published on

深入剖析JVM中的堆与堆空间:掌握Java内存管理的核心要点

Authors
  • avatar
    Name
    NoOne
    Twitter

为了更高效的运行应用程序,JVM 选择将内存划分为栈空间和堆空间。 每当我们声明新变量和对象、调用新方法、声明 String 或执行类似操作时,JVM 都会在栈或堆空间里面操作指定内存。在这篇文章里面,我们将简要介绍 JVM 的内存模型以及它们的主要功能,然后我们将介绍它们是如何存储在内存里面,以及会在哪里使用到它们。最后,我们将分几个维度总结它们之间的主要区别。

1. 栈空间

JVM 中的栈空间用于静态内存分配和线程的执行。 它包含方法的原始值以及对象的引用。

对该内存的访问是按后进先出 (LIFO) 顺序进行的。每当我们调用新方法时,都会在堆栈顶部创建一个新块,其中包含特定于该方法的值,例如原始变量和对对象的引用。

当方法完成执行时,其相应的堆栈帧将被刷新,流程返回到调用方法,这样空间就又可用于下一个方法运行。

1.1 栈空间的主要特性

栈空间的其他一些特性包括:

  • 它随着新方法的调用和返回分别增长和缩小。
  • 堆栈中的变量只有在创建它们的方法正在运行时才存在。
  • 当方法执行完成时,它会自动分配和释放。
  • 如果此内存已满,Java 将抛出 java.lang.StackOverFlowError.
  • 与堆内存相比,访问此内存的速度更快。
  • 该内存是线程安全的,因为每个线程都在自己的堆栈中运行。

2. 堆空间

堆空间用于 Java 对象在运行时的动态内存分配。新对象总是在堆空间中创建,而这些对象的引用则存储在栈空间中。

这些对象具有全局访问权限,我们可以从应用程序中的任何位置访问它们。

我们可以将这个内存模型分解成更小的部分,称为世代,它们是:

  1. 新生代: 这是所有新对象被分配和老化的地方。填满时会发生 Minor GC。
  2. 老年代: 这是存储长期存活对象的地方。当对象存储在新生代时,会设置对象年龄的阈值,当达到该阈值时,对象会被移动到老年代。

2.1 堆空间的主要特性

堆空间的其他一些特性包括:

  • 它通过复杂的内存管理技术访问,包括新生代、老年代等等。
  • 如果堆空间已满,Java 抛出 java.lang.OutOfMemoryError.
  • 访问这块内存比栈内存慢
  • 与堆栈相比,此内存不会自动释放。它需要垃圾收集器来释放未使用的对象,以保持内存使用的效率。
  • 与堆栈不同,堆不是线程安全的,需要通过正确同步代码来保护。

3. 内存分配示例


基于我们目前所学的,让我们通过分析一个简单的 Java 代码来展示 JVM 如何管理内存:

class Person {
    int id;
    String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class PersonBuilder {
    private static Person buildPerson(int id, String name) {
        return new Person(id, name);
    }

    public static void main(String[] args) {
        int id = 66;
        String name = "Zhang";
        Person person = null;
        person = buildPerson(id, name);
    }
}

让我们逐步分析一下:

  1. 当我们进入 main() 方法时,会在栈内存中创建一个空间来存储该方法的原语和引用。
    • 栈内存直接存储整数的原始值 id
    • Person 类型的引用变量 person 也将在栈空间中创建,它将指向堆中的实际对象。
  2. main() 调用参数化构造函数 Person(int, String) 将在前一个堆栈的顶部分配更多内存。将存储如下内容:
    • 栈空间中调用对象的 this 对象引用
    • 栈空间中的原始值 id
    • String 参数 name 的引用变量将指向堆内存中字符串池中的实际字符串
  3. main 方法进一步调用了 buildPerson() 静态方法,其进一步分配将在前一个之上的栈空间中进行。这将再次以上述方式存储变量。
  4. 堆内存会存储新创建的 Person 类型的 person 的所有实例变量

实际在内存中的分配如下图:

java堆栈图

4. 小结


让我们快速总结一下栈空间和堆空间的主要区别:

参数栈空间堆空间
使用场景在线程执行期间使用整个应用程序在运行时使用堆空间
大小限制栈空间有大小限制,具体取决于操作系统,通常小于堆空间通常没有大小限制
存储内容仅存储原始变量和在堆空间中创建的对象的引用所有新创建的对象都存储在这里
存取顺序它使用后进先出 (LIFO) 内存分配系统进行访问该内存是通过复杂的内存管理技术访问的,主要包括新生代和老年代。
生命周期栈空间只在当前方法运行时才存在只要应用程序运行,堆空间就存在
效率与堆相比,分配速度要快得多与堆栈相比分配更慢
分配和回收当一个方法被调用和返回时,这个内存将被自动分配和回收堆空间是在新对象创建时分配的,当它们不再被引用时将被回收

栈空间和堆空间是 Java 分配内存的两种基本方式,熟悉它们的工作原理,将有利于我们开发出高效运行的 Java 应用程序。

Share this content