java的内存模型 - stack和heap
好文直译,来源这篇文章: Stack Memory and Heap Space in Java (https://www.baeldung.com/java-stack-heap)
1. 指南
为了以最佳方式运行应用程序,JVM将内存分为堆栈和堆内存。 每当我们声明新的变量和对象,调用新的方法,声明String或执行类似的操作时,JVM就会从堆栈内存或堆空间中为这些操作指定内存。
在本教程中,我们将讨论这些内存模型。 我们将列举它们之间的一些关键区别,它们在RAM中的存储方式,它们提供的功能以及在何处使用它们。
2. java中的栈内存 - Stack
Java中的堆栈内存用于静态内存分配和线程执行。 它包括给某个方法操作的原始值(byte、short、int、long、boolean、char、float、double)以及对从该方法引用的堆中对象的引用。
对该存储器的访问按后进先出(LIFO)顺序进行。 每当调用新方法时,都会在堆栈顶部创建一个新块(压栈),其中包含特定于该方法的值,例如原始变量和对对象的引用。
方法执行完后,将释放其对应的堆栈贞(出栈),流程返回到调用方法,并且下一个方法可以使用空间。
2.1. 栈内存几个特性
除了我们到目前为止讨论的内容以外,以下是堆栈存储器的其他一些特性:
- 随着新的方法有序的压栈和返回,它会增长和收缩
- 堆栈中的变量仅在创建它们的方法运行结束之前(返回)才存在 (此处原文表达不准确,已修改)
- 栈内存被自动分配和释放 (原文表达不准确,已修改,“自动释放”应该是相对堆内存的垃圾回收来说说的吧)
- 如果此内存已满,则Java抛出java.lang.StackOverFlowError
- 与堆内存相比,对该内存的访问速度很快
- 该内存是线程安全的,因为每个线程都在自己的堆栈中运行
3. java的堆空间 - Heap
Java中的堆空间用于在运行时为Java对象和JRE类动态分配内存。 新对象总是在堆空间中创建,并且对该对象的引用存储在栈内存中。
这些对象具有全局访问权限,可以从应用程序中的任何位置进行访问。
此内存模型进一步细分为称为Generation 的较小部分,它们是:
- 年轻代 – 在这里存放新创建的对象并给这些对象计时(逐渐增加对象的age,到了某个阈值(一般是15)就会被放到老年代中)。 填满后会发生minor垃圾回收
- 老年代 – 这是保存长期存在的对象的位置。 当对象存储在“年轻代”中时,将设置该对象的年龄阈值,当达到该阈值时,该对象将移至老一代
- 永生代 – 包含用于运行时类和应用程序方法的JVM元数据
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也将在堆栈内存中创建,该内存将指向堆中的实际对象
- 从main()对参数化构造函数Person(int,String)的调用将在上一个堆栈的顶部分配内存。它将存储:
- 堆栈内存中调用对象的this对象引用
- 堆栈存储器中的原始值id
- String参数名称的引用变量,它将指向堆内存中字符串池中的实际字符串
- 堆栈内存中调用对象的this对象引用
- main方法进一步调用buildPerson()静态方法,对此方法的进一步分配将在上一个方法的顶部在堆栈内存中进行。这将再次以上述方式存储变量。(我感觉Person()是后压栈的,原文应该不对)
- 但是,对于新创建的Person类型的对象person,所有实例变量都将存储在堆内存中。
下图说明了这种分配:

5. 总结
在总结本文之前,让我们快速总结一下栈内存和堆空间之间的区别:
| 比较维度 | 栈 | 堆 |
|---|---|---|
| 应用 | 栈被分块访问,在一个线程一次执行期间只访问特定的栈贞 | 应用运行时可以访问到整个堆空间 |
| 大小 | 栈空间有限制,由操作系统决定,一般比堆小 | 堆没有大小限制 |
| 存储 | 只用来存储原始值和堆中对象的引用 | 所有被创建的对象 |
| 顺序 | 先进先出(LIFO) | 可通过复杂的内存管理机制访问该内存,包括年轻代,老年代以及永久一代。 |
| 生命周期 | 分配的栈内存的只在方法被执行时存在 | 对空间在程序运行是一直存在 |
| 效率 | 与堆相比,分配速度要快得多 | 与堆栈相比分配速度较慢 |
| 分配和释放 | 分别调用和返回方法时,将自动分配和释放此内存 |
当新对象被创建是分配堆空间,当栈中没有对象引用是垃圾回收器对其内存回收 |
当不再引用新对象时,由垃圾收集器创建并释放新对象时分配堆空间

浙公网安备 33010602011771号