Java 堆栈内存分配

很多人在Java的书籍中看到过很多关于堆和栈内存的教程以及参考说明, 但是很难解释什么是程序的堆内存以及栈内存

一: Java 堆内存空间

Java程序运行时使用java Heap 内存为对象以及JRE类分配内存, 不论我们在何时创建何种类型的对象, 他总是在堆内存中创建的

Java 垃圾收集器运行在堆内容空间, 释放那些没有任何引用的对象所使用的内存。 在堆内存空间创建的任何对象都具有全局访问权限, 并且可以从程序的任何位置引用

二: Java 栈内存空间

Java 栈内存空间用于执行线程, 栈内存始终遵循LIFO(Last-in-first-out) 顺序, 每当一个方法被执行, 会在栈内存中创建一个新的block 用于保存在函数中定义的基本数据类型变量以及对象的引用变量

当方法结束时, this block 改变它的状态为未使用并且可用于执行下一个方法

堆内存大小与堆内存相比非常少。

三: Java程序中的堆和堆栈内存

通过一个简单的程序来理解堆 栈内存的使用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Created by huanjulu on 12/10/17.
*/
public class HeapStackTestMemory {
public static void main(String[] args) { //Line 1
int i = 1; //Line 2
Object obj = new Object(); //Line 3
HeapStackTestMemory mem = new HeapStackTestMemory(); //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 程序, 下图显示了stack Heap 内存空间的使用情况

我们来看看执行程序的步骤。

  • 一旦我们开始运行程序, 它会把所有的运行时类加载到堆内存空间, 在 Line 1 行找到main() 方法, Java Runtime 创建由main() 方法线程使用的栈内存空间
  • 在第二行 我们创建了原始数据类型的局部变量, 所以它将被存储在main() 方法的栈内存空间
  • 在第3行我们创建了一个Object 类型的对象, 所以它被创建在Heap 堆内存空间中 并且 Stack 栈内存空间包含对它的引用, 当我们在第4行中创建Memory 对象时, 会发生类似的过程
  • 现在我们在第5行调用foo() 方法, 此时会在stack 栈创建一个block 供foo() 方法使用
  • Java 是通过值传递, 在第6行, 会在foo() 栈中创建一个对Object 对象的新的引用
  • 在第7行 , 一个string 类型的对象被创建, 此时 会在foo() 栈内存中创建它的一个引用 str
  • foo() 方法在第8行执行完毕, 此时, 程序会释放stack 栈内存中为foo() 方法分配的栈内存空间
  • 在第9行, main() 方法执行完毕, 为main()方法创建的堆栈内存被销毁, 此时 这个java 程序结束运行, Java Runtime 会释放所有的内存

 
四: Java Heap Difference with Stack Memory Space

基于上述的说明, 可以很容易的总结出堆栈内存的以下差异

1, 堆内存属于java 应用程序所使用, 栈内存属于线程所私有的, 它的生命周期与线程相同
2, 不论何时创建一个对象, 它总是存储在堆内存空间 并且栈内存空间包含对它的引用 . 栈内存空间只包含方法原始数据类型局部变量以及堆空间中对象的引用变量
3, 在堆中的对象可以全局访问, 栈内存空间属于线程所私有
4, jvm 栈内存结构管理较为简单, 遵循LIFO 的原则, 堆空间内存管理较为复杂 , 细分为:新生代和老年代 etc..
5, 栈内存生命周期短暂, 而堆内存伴随整个用用程序的生命周期
6, 二者抛出异常的方式, 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常, 堆内存抛出OutOfMemoryError异常