Java中的栈内存和堆空间

2025/04/02

1. 概述

为了以最佳方式运行应用程序,JVM将内存分为栈内存和堆内存。每当我们声明新变量和对象、调用新方法、声明String或执行类似操作时,JVM都会从栈内存或堆空间中为这些操作指定内存

在本教程中,我们将研究这些内存模型。首先,我们将探索它们的主要功能。然后我们将了解它们如何存储在RAM中,以及在哪里使用它们。最后,我们将讨论它们之间的主要区别。

2. Java中的栈内存

Java中的栈内存用于静态内存分配和线程的执行,它包含特定于方法的原始值和对从堆中的方法引用的对象的引用。

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

当方法完成执行时,其对应的栈帧被刷新,流程返回到调用方法,并为下一个方法提供空间。

2.1 栈内存的主要特性

栈内存的其他一些特性包括:

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

3. Java中的堆空间

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

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

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

  1. 新生代:这是所有新对象分配和老化的地方,当它填满时,会发生一次minor GC。
  2. 老年代:这是存储长期存活对象的地方,当对象存储在新生代时,会设置一个对象的年龄阈值,当达到该阈值时,将对象移至老年代。
  3. 永久代:这包括运行时类和应用程序方法的JVM元数据。

这些不同的部分也在JVM、JRE和JDK之间的差异一文中进行了讨论。

我们始终可以根据需要调整堆内存的大小,有关更多信息,请访问本文

3.1 Java堆内存的主要特性

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

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

4. 示例

基于我们目前所学的知识,让我们分析一个简单的Java代码来评估内存是如何管理的:

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 = 23;
        String name = "John";
        Person person = null;
        person = buildPerson(id, name);
    }
}

让我们一步一步地分析一下:

  1. 当我们进入main()方法时,会在栈内存中创建一个空间来存储该方法的原始值和引用。

    • 栈内存直接存放整数id的原始值。
    • Person类型的引用变量person也会在栈内存中创建,最初指向null,后来更新为指向堆中的实际对象。
  2. main方法进一步调用buildPerson()静态方法,该方法的分配将在前一个内存顶部的栈内存中进行。

  3. buildPerson()调用参数化构造函数Person(int, String),该构造函数将在前一个栈顶部分配更多内存,这将存储:

    • 栈内存中调用对象的this对象引用。
    • 栈内存中的原始值id。
    • String参数name的引用变量,它将指向堆内存中字符串池中的实际字符串。
  4. 但是,堆内存将存储新创建的Person类型的对象person的所有实例变量。

我们来看看下图中的分配:

5. 比较

在结束本文之前,让我们快速总结一下栈内存和堆空间之间的区别:

范围 栈内存 堆空间
应用 线程执行期间,堆栈被分部分使用,每次使用一个 整个应用程序在运行时使用堆空间
大小 栈的大小限制取决于操作系统,通常比堆小 堆没有大小限制
存储 仅存储原始变量和对在堆空间中创建的对象的引用 所有新创建的对象都存储在这里
顺序 使用后进先出(LIFO)内存分配系统进行访问 该内存通过复杂的内存管理技术访问,包括新生代、老年代以及永久代。
存活 栈内存仅在当前方法运行时存在 只要应用程序运行,堆空间就存在
效率 与堆相比,分配速度更快 与堆栈相比,分配速度较慢
分配/取消分配 当方法被调用和返回时,将自动分配和释放此内存 堆空间在创建新对象时分配,当不再被引用时由GC释放

6. 总结

栈和堆是Java分配内存的两种方式,在本文中,我们了解了它们的工作原理,以及何时使用它们来开发更好的Java程序。

要了解有关Java内存管理的更多信息,请在此处查看这篇文章。我们还谈到了JVM垃圾回收器,本文对此进行了简要讨论。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章