Java 堆空间与堆栈 - Java 中的内存分配
前段时间,我写了几篇关于Java 垃圾收集和Java 按值传递的文章。之后,我收到了很多电子邮件,要求我解释Java 堆空间、Java 堆栈内存、Java 中的内存分配以及它们之间的区别。你会看到很多关于 Java、Java EE 书籍和教程中堆和堆栈内存的参考资料,但很难从程序的角度完整解释什么是堆和堆栈内存。
Java 堆空间
Java 运行时使用 Java 堆空间为对象和 JRE 类分配内存。每当我们创建一个对象时,它总是在堆空间中创建。垃圾收集在堆内存上运行,以释放没有任何引用的对象使用的内存。在堆空间中创建的任何对象都具有全局访问权限,并且可以从应用程序的任何位置引用。
Java 堆栈内存
Java 堆栈内存用于执行线程。它们包含方法特定的短暂值以及对从方法引用的堆中其他对象的引用。堆栈内存始终按 LIFO(后进先出)顺序引用。每当调用一个方法时,都会在堆栈内存中为该方法创建一个新块,以保存本地原始值并引用方法中的其他对象。方法结束后,该块将变为未使用状态并可用于下一个方法。与堆内存相比,堆栈内存大小非常小。
Java程序中的堆和栈内存
让我们通过一个简单的程序来了解堆和堆栈的内存使用情况。
package com.journaldev.test;
public class Memory {
public static void main(String[] args) { // Line 1
int i=1; // Line 2
Object obj = new Object(); // Line 3
Memory mem = new Memory(); // Line 4
mem.foo(obj); // Line 5
} // Line 9
private void foo(Object param) { // Line 6
String str = param.toString(); //// Line 7
System.out.println(str);
} // Line 8
}
下图显示了上述程序中的堆栈和堆内存,以及它们如何用于存储原始变量、对象和引用变量。让我们来看看程序执行的步骤。
- 一旦我们运行程序,它就会将所有运行时类加载到堆空间中。当在第 1 行找到 main() 方法时,Java 运行时会创建堆栈内存供 main() 方法线程使用。
- 我们在第 2 行创建原始局部变量,因此它被创建并存储在 main() 方法的堆栈内存中。
- 由于我们在第 3 行创建了一个对象,因此它是在堆内存中创建的,而堆栈内存包含对它的引用。当我们在第 4 行创建内存对象时,也会发生类似的过程。
- 现在,当我们在第 5 行调用 foo() 方法时,将在堆栈顶部创建一个块,供 foo() 方法使用。由于 Java 是按值传递的,因此在第 6 行的 foo() 堆栈块中会创建一个对 Object 的新引用。
- 第 7 行创建了一个字符串,它进入堆空间中的字符串池,并在 foo() 堆栈空间中为其创建一个引用。
- foo() 方法在第 8 行终止,此时堆栈中为 foo() 分配的内存块变为空闲。
- 在第 9 行,main() 方法终止,为 main() 方法创建的堆栈内存被销毁。此外,程序在此行结束,因此 Java Runtime 释放所有内存并结束程序的执行。
Java堆空间和堆栈内存之间的区别
基于以上解释,我们可以很容易地得出堆和栈内存之间的以下区别。
- 堆内存由应用程序的所有部分使用,而堆栈内存仅由一个执行线程使用。
- 每当创建一个对象时,它总是存储在堆空间中,而堆栈内存包含对它的引用。堆栈内存仅包含本地原始变量和堆空间中对象的引用变量。
- 存储在堆中的对象是全局可访问的,而堆栈内存不能被其他线程访问。
- 堆栈中的内存管理采用后进先出 (LIFO) 方式,而堆内存则更复杂,因为它是全局使用的。堆内存分为年轻代、老一代等,更多详细信息请参阅Java 垃圾收集。
- 堆栈内存的寿命很短,而堆内存从应用程序执行开始到结束一直存在。
- 我们可以使用-Xms和-Xmx JVM 选项来定义堆内存的启动大小和最大大小。我们可以使用-Xss来定义堆栈内存大小。
- 当堆栈内存已满时,Java 运行时会抛出异常
java.lang.StackOverFlowError
,而当堆内存已满时,则会抛出java.lang.OutOfMemoryError: Java Heap Space
错误。 - 与堆内存相比,堆栈内存大小非常小。由于内存分配简单(LIFO),堆栈内存与堆内存相比非常快。
这就是关于Java 应用程序的Java 堆空间与堆栈内存的全部内容,我希望它能够消除您对执行任何 Java 程序时内存分配的疑虑。