Java堆VS栈-Java中的内存分配
原文:Java Heap Space vs Stack – Memory Allocation in Java
在Java EE书籍和java教程中都能看到很多关于堆和栈的描述,从程序运行时的角度如何理解堆内存和栈到底是什么呢?
Java堆内存
java堆是在运行时给对象和JRE类使用的内存。我们创建的任何对象都存储于堆内存中。垃圾收集器也是收集堆内存中的那些没有任何引用的对象所占用的内存。堆中对象的作用域都是全局的,在应用运行时可以随便访问。
Java 栈
Java栈内存是每个线程执行的时候分配的。栈中存储方法的形参、局部变量,它们的作用域只限于当前方法。栈的引用顺序是LIFO(Last-In-First-Out 先进后出)。当调用一个方法时,就会在栈中分配一块内存用于存放方法中的局部变量(包括基本类型和引用类型)。一旦方法调用结束这部分栈内存就会被释放用于其他方法调用。栈内存的空间远远小于堆内存空间。
接下来我们通过一段简单的程序来解释一下运行时的堆和栈
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
}
下图展示了以上代码的堆和栈内存分配,并且展示了其中的基本类型值、对象、变量引用都是如何存储的。
接下来按照程序执行步骤来分析如下:
程序运行就会加载所有Java运行时类到堆中。当执行line 1的main()方法时,Java虚拟机会分配main()所需的栈内存。
当执行到line 2时,创建了一个基本类型的局部变量 i,该变量被存储到main()方法的栈内存中。
在line 3我们创建了一个对象,该对象存放于堆内存中,同时在栈内存中创建了一个指向这个对象的变量obj。line 4做了同样的事。
在line 5调用foo()方法时,会在栈顶分配foo()方法所需的栈空间。由于java是值传递(参考:java是值传递还是引用传递?),执行到line 6时就会在foo()方法的栈中创建一个新的obj对象引用。
在line 7创建了一个字符串,该字符串存放于堆内存中的字符串常量池中,同时foo()方法中创建了一个该字符串的引用类型变量。
在line 8的时候foo()方法调用结束,此时foo()方法在栈内存中的空间被释放。
在line 9行main()方法执行结束同时释放main()方法的栈内存。至此整个程序运行结束,此时java虚拟机会释放该程序使用的所有内存并结束程序。
Java堆和栈的区别
基于以上分析,我们很容易得出堆和栈内存之间的区别:
1. 堆内存由应用程序的所有部分使用,而栈内存仅由一个执行线程使用。
2. 每当一个对象被创建,它总是存储在堆内存,而栈内存仅存储该对象的引用。栈内存只存储本地基本类型变量和堆内存中对象的引用。
3. 存储在堆中的对象是全局可访问的,而栈内存不能被其他线程访问。
4. 栈中的内存管理以LIFO方式完成,而在堆内存中则更复杂,因为堆中的数据是全局的。堆内存分为年轻代,老年代等,更多细节请参考Java垃圾收集。
5. 栈内存使用周期是很短暂的,而堆内存的使用周期是从程序启动到程序执行结束。
6. 我们可以通过-Xms和-Xmx JVM选项来定义堆内存的启动大小和最大值。可以通过-Xss来定义栈内存的大小。
7. 当栈内存已满时,Java运行时会抛出java.lang.StackOverFlowError,而如果堆内存已满,则会抛出java.lang.OutOfMemoryError:Java Heap Space错误。
8. 与堆内存相比,栈内存非常小。由于栈内存分配(LIFO)的简单性,与堆内存相比,栈内存非常快。
这是所有的堆和栈在java应用程序中的对比,希望能消除你关于Java程序执行时内存分配的疑虑。