为了更高效的运行应用程序,JVM 选择将内存划分为栈空间和堆空间。 每当我们声明新变量和对象、调用新方法、声明 String 或执行类似操作时,JVM 都会在栈或堆空间里面操作指定内存。
在这篇文章里面,我们将简要介绍 JVM 的内存模型以及它们的主要功能,然后我们将介绍它们是如何存储在内存里面,以及会在哪里使用到它们。最后,我们将分几个维度总结它们之间的主要区别。
1. 栈空间
JVM 中的栈空间用于静态内存分配和线程的执行。 它包含方法的原始值以及对象的引用。
对该内存的访问是按后进先出 (LIFO) 顺序进行的。每当我们调用新方法时,都会在堆栈顶部创建一个新块,其中包含特定于该方法的值,例如原始变量和对对象的引用。
当方法完成执行时,其相应的堆栈帧将被刷新,流程返回到调用方法,这样空间就又可用于下一个方法运行。
1.1 栈空间的主要特性
栈空间的其他一些特性包括:
- 它随着新方法的调用和返回分别增长和缩小。
- 堆栈中的变量只有在创建它们的方法正在运行时才存在。
- 当方法执行完成时,它会自动分配和释放。
- 如果此内存已满,Java 将抛出 java.lang.StackOverFlowError.
- 与堆内存相比,访问此内存的速度更快。
- 该内存是线程安全的,因为每个线程都在自己的堆栈中运行。
2. 堆空间
堆空间用于 Java 对象在运行时的动态内存分配。新对象总是在堆空间中创建,而这些对象的引用则存储在栈空间中。
这些对象具有全局访问权限,我们可以从应用程序中的任何位置访问它们。
我们可以将这个内存模型分解成更小的部分,称为世代,它们是:
- 新生代: 这是所有新对象被分配和老化的地方。填满时会发生 Minor GC。
- 老年代: 这是存储长期存活对象的地方。当对象存储在新生代时,会设置对象年龄的阈值,当达到该阈值时,对象会被移动到老年代。
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 = 23;
String name = "John";
Person person = null;
person = buildPerson(id, name);
}
}
让我们逐步分析一下:
- 当我们进入 main() 方法时,会在栈内存中创建一个空间来存储该方法的原语和引用。
- 栈内存直接存储整数的原始值 id
- Person 类型的引用变量 person 也将在栈空间中创建,它将指向堆中的实际对象。
- 从 main() 调用参数化构造函数 Person(int, String) 将在前一个堆栈的顶部分配更多内存。将存储如下内容:
- 栈空间中调用对象的 this 对象引用
- 栈空间中的原始值 id
- String 参数 name 的引用变量将指向堆内存中字符串池中的实际字符串
- main 方法进一步调用了 buildPerson() 静态方法,其进一步分配将在前一个之上的栈空间中进行。这将再次以上述方式存储变量。
- 堆内存会存储新创建的 Person 类型的 person 的所有实例变量
实际在内存中的分配如下图:
4. 小结
让我们快速总结一下栈空间和堆空间的主要区别:
参数 | 栈空间 | 堆空间 |
---|---|---|
使用场景 | 在线程执行期间使用 | 整个应用程序在运行时使用堆空间 |
大小限制 | 栈空间有大小限制,具体取决于操作系统,通常小于堆空间 | 通常没有大小限制 |
存储内容 | 仅存储原始变量和在堆空间中创建的对象的引用 | 所有新创建的对象都存储在这里 |
存取顺序 | 它使用后进先出 (LIFO) 内存分配系统进行访问 | 该内存是通过复杂的内存管理技术访问的,主要包括新生代和老年代。 |
生命周期 | 栈空间只在当前方法运行时才存在 | 只要应用程序运行,堆空间就存在 |
效率 | 与堆相比,分配速度要快得多 | 与堆栈相比分配更慢 |
分配和回收 | 当一个方法被调用和返回时,这个内存将被自动分配和回收 | 堆空间是在新对象创建时分配的,当它们不再被引用时将被回收 |
栈空间和堆空间是 Java 分配内存的两种基本方式,熟悉它们的工作原理,将有利于我们开发出高效运行的 Java 应用程序。