堆内存和栈内存

Java中,您会看到大量关于堆内存和栈内存的引用,Java把内存分成两种,一种叫做栈内存,一种叫做堆内存。

Java 堆内存(Heap Memory)

Java运行时会在堆内存中将内存分配给对象和子类。每当我们创建任何对象时,它总是在堆空间中创建的。

垃圾收集在堆内存上运行,以释放没有任何引用的对象使用的内存。堆空间中创建的任何对象都具有全局访问权限,可以从应用程序的任何位置引用。

Java 栈内存(Stack Memory)

Java栈内存用于执行线程。它们包含短期存活的方法的特定值,以及堆中方法对其他对象的引用。

栈内存总是按后进先出顺序引用。每当调用一个方法时,都会在栈内存中创建一个新块,以便该方法保存本地基元值以及该方法对其他对象的引用。

一旦方法结束,该块就不再使用,就可用于下一个方法。与堆内存相比,栈内存非常小。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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
}

下图展示了上述程序的堆栈内存的引用

img

程序执行的过程如下:

  • 一旦程序开始运行,它将会加载所有运行时类到堆内存,第1行发现了main方法,Java Runtime 创建了栈内存给main方法线程使用。
  • 第2行,我们创建了int型本地变量,它被创建并存储在栈内存的main方法里。
  • 第3行,我们创建一个对象,它创建在堆内存,栈内存保存它的引用。Memory 对象类似。
  • 第5行,我们调用foo方法,在栈内存头部创建一个内存块给foo方法使用,既然Java通过值传递,第6行,对象的新引用将在foo方法的栈内存块内创建。
  • 第7行,创建了字符串,它将会进入堆中的字符串常量池,foo方法的栈空间内将创建一个它的引用。
  • foo方法在第8行终止。栈中分配的内存块将会被释放。
  • 在第9行,main方法终止,分配给main方法的栈内存将会被销毁。同时,程序也在这一行结束,因此 Java Runtime将会释放所有内存,然后结束程序的执行。

堆内存和栈内存的区别

  1. 应用程序的所有地方都会使用堆内存,而只有线程的执行会使用栈内存。
  2. 当一个对象创建时,它存放在堆内存中,引用放在栈内存中。栈内存只包含本地原始变量和引用变量。
  3. 存放在堆内存的对象是全局的,而栈内存中的不能被其他线程获取。
  4. 栈中的内存管理是以后进先出的方式完成的,而堆内存中的内存管理更复杂,因为它是全局使用的。堆内存分为新生代、老年代等,更多信息可以参考Java 垃圾回收。
  5. 栈内存是短暂存在的,而堆内存从应用程序执行的开始到结束都是存在的。
  6. 我们可以使用-Xms-Xmx来定义堆内存启动时的大小和内存最大值。可以使用-Xss去定义栈内存大小。
  7. 当栈内存被填满的时候,将会抛出 java.lang.StackOverFlowError,然而,当堆内存被填满的时候,它会抛出 java.lang.OutOfMemoryError: Java Heap Space 错误。
  8. 和堆内存大小相比,栈内存大小非常小。由于内存分配(LIFO)的简单性,与堆内存相比,栈内存非常快。