1、堆
Java堆是用来在运行时给Java对象(Objects)和JRE 类分配内存空间的。
垃圾收集器就是收集堆中没有被引用到的对象。对应用程序来讲,堆是全局的,它可以在应用的任何地方被引用到。
2、栈
java栈内存用于一个线程执行。他们包含了方法中局部变量的值和堆中对象的引用。
栈内存是后进先出(LIFO)顺序的。当一个方法被调用时,就为它分配一块栈空间用于保存方法中的局部变量值和引用。当一个方法调用结束时,那这块栈空间就没用了,他就分配给下一个方法使用了。
栈内存的大小相对于堆内存是非常小的。
3、Java程序中的堆和栈
用一个实际例子来理解堆内存和栈内存的用法。
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
}
下面这张图显示了堆和栈的引用情况,以及他们是如何存储局部变量、对象和对象的引用的。
我们看下程序的执行步骤:
- 当程序开始执行时,虚拟机就把所有的运行时类加载到堆内存中;当发现Line 1的main() 方法时,Java 运行时就创建栈空间,用来分配给当前线程的main()方法来使用。
- Line 2 创建了一个局部变量,它被存储到 main() 的栈内存中。
- Line 3 创建了一个Java 对象,它被存储到堆内存中。
- 当Line 5 中的 foo( ) 方法被调用时,就在栈顶为其创建一块内存。由于java是按值传递的,所以在Line 6, 在foo() 的栈内存中为参数对象创建了一个新的引用。
- Line 7 创建了一个 String,他被存放到 String Pool中,同时在 foo() 的栈内存中为其创建了一个引用。
- Line 8 中,foo() 方法执行结束了,这时,为其分配的栈内存就变得空闲了。
- Line 9 中,main() 执行结束了,这时为其分配的栈内存就被销毁了。同时,程序也结束了,因此,java运行时释放所有内存和结束程序的执行。
4、堆和栈的不同点
基于以上的解释说明,我们可以很容易地总结出如下的不同点:
- 堆是全局的,在应用的各个部分中都会用到;栈是线程私有的,是用来给线程执行的。
- 堆是用来存储对象的,栈是用来存储堆中对象的引用和局部变量的。
- 堆中存储的对象是全局可访问的,而栈内存不能被其他线程访问。
- 栈内存的管理是后进先出(LIFO)的,而堆内存更复杂,因为它是全局的。他被划分为年轻带,老年代等。
- 栈内存是短生命周期(short-lived )的,而堆内存的生命周期是从应用开始执行到应用结束。
- 可以使用 -Xms 和 -Xmx JVM参数来设置堆内存的起始大小和最大值。 可以使用 -Xss 来定义栈内存的大小。
- 当栈内存用满时,Java 运行时会抛出 java.lang.StackOverFlowError ;而堆用满时,会抛出java.lang.OutOfMemoryError: Java Heap Space 错误。
- 栈内存大小相比于堆内存的大小,它是非常小的;堆栈内存与堆内存相比非常快。