1. 概述
为了以最佳方式运行应用程序,JVM将内存分为栈内存和堆内存。每当我们声明新变量和对象、调用新方法、声明String或执行类似操作时,JVM都会从栈内存或堆空间中为这些操作指定内存。
在本教程中,我们将研究这些内存模型。首先,我们将探索它们的主要功能。然后我们将了解它们如何存储在RAM中,以及在哪里使用它们。最后,我们将讨论它们之间的主要区别。
2. Java中的栈内存
Java中的栈内存用于静态内存分配和线程的执行,它包含特定于方法的原始值和对从堆中的方法引用的对象的引用。
对该内存的访问遵循后进先出(LIFO)顺序,每当我们调用一个新方法时,都会在栈顶部创建一个新块,其中包含特定于该方法的值,例如原始变量和对对象的引用。
当方法完成执行时,其对应的栈帧被刷新,流程返回到调用方法,并为下一个方法提供空间。
2.1 栈内存的主要特性
栈内存的其他一些特性包括:
- 它会随着新方法的调用和返回分别增长和缩小。
- 栈内的变量仅在创建它们的方法运行时存在。
- 当方法完成执行时,它会自动分配和释放。
- 如果此内存已满,Java将抛出java.lang.StackOverFlowError。
- 与堆内存相比,访问此内存的速度很快。
- 该内存是线程安全的,因为每个线程都在自己的栈中运行。
3. Java中的堆空间
堆空间用于Java对象和JRE类在运行时的动态内存分配,新对象总是在堆空间中创建,而对这些对象的引用存储在栈内存中。
这些对象具有全局访问权限,我们可以从应用程序的任何位置访问它们。
我们可以将这个内存模型分解成更小的部分,称为代,它们是:
- 新生代:这是所有新对象分配和老化的地方,当它填满时,会发生一次minor GC。
- 老年代:这是存储长期存活对象的地方,当对象存储在新生代时,会设置一个对象的年龄阈值,当达到该阈值时,将对象移至老年代。
- 永久代:这包括运行时类和应用程序方法的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);
}
}
让我们一步一步地分析一下:
-
当我们进入main()方法时,会在栈内存中创建一个空间来存储该方法的原始值和引用。
- 栈内存直接存放整数id的原始值。
- Person类型的引用变量person也会在栈内存中创建,最初指向null,后来更新为指向堆中的实际对象。
-
main方法进一步调用buildPerson()静态方法,该方法的分配将在前一个内存顶部的栈内存中进行。
-
buildPerson()调用参数化构造函数Person(int, String),该构造函数将在前一个栈顶部分配更多内存,这将存储:
- 栈内存中调用对象的this对象引用。
- 栈内存中的原始值id。
- String参数name的引用变量,它将指向堆内存中字符串池中的实际字符串。
-
但是,堆内存将存储新创建的Person类型的对象person的所有实例变量。
我们来看看下图中的分配:
5. 比较
在结束本文之前,让我们快速总结一下栈内存和堆空间之间的区别:
范围 | 栈内存 | 堆空间 |
---|---|---|
应用 | 线程执行期间,堆栈被分部分使用,每次使用一个 | 整个应用程序在运行时使用堆空间 |
大小 | 栈的大小限制取决于操作系统,通常比堆小 | 堆没有大小限制 |
存储 | 仅存储原始变量和对在堆空间中创建的对象的引用 | 所有新创建的对象都存储在这里 |
顺序 | 使用后进先出(LIFO)内存分配系统进行访问 | 该内存通过复杂的内存管理技术访问,包括新生代、老年代以及永久代。 |
存活 | 栈内存仅在当前方法运行时存在 | 只要应用程序运行,堆空间就存在 |
效率 | 与堆相比,分配速度更快 | 与堆栈相比,分配速度较慢 |
分配/取消分配 | 当方法被调用和返回时,将自动分配和释放此内存 | 堆空间在创建新对象时分配,当不再被引用时由GC释放 |
6. 总结
栈和堆是Java分配内存的两种方式,在本文中,我们了解了它们的工作原理,以及何时使用它们来开发更好的Java程序。
要了解有关Java内存管理的更多信息,请在此处查看这篇文章。我们还谈到了JVM垃圾回收器,本文对此进行了简要讨论。
Post Directory
