Java堆空间与堆栈– Java中的内存分配
有时我写了几篇关于Java垃圾收集的文章,Java是"按价值传递"。
之后,我收到了很多电子邮件,以解释Java堆空间,Java堆栈内存,Java中的内存分配以及它们之间的区别。
您将在Java,Java EE书籍和教程中看到很多有关堆和堆栈内存的参考,但是几乎没有就程序而言完全解释堆和堆栈内存。
Java堆空间
Java运行时使用Java堆空间为Objects和JRE类分配内存。
每当我们创建对象时,它总是在堆空间中创建。
垃圾回收在堆内存上运行,以释放没有任何引用的对象使用的内存。
在堆空间中创建的任何对象都具有全局访问权限,并且可以从应用程序的任何位置进行引用。
Java堆栈内存
Java Stack内存用于执行线程。
它们包含短期的特定于方法的值以及对从该方法引用的堆中其他对象的引用。
堆栈存储器始终按LIFO(后进先出)顺序引用。
每当调用方法时,都会在堆栈存储器中创建一个新块,以容纳该方法的本地基本值并引用该方法中的其他对象。
方法结束后,该块将立即变为未使用状态,并可用于下一个方法。
与堆内存相比,堆栈内存的大小要小得多。
Java程序中的堆和堆栈内存
让我们通过一个简单的程序来了解堆和堆栈的内存使用情况。
package com.theitroad.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行中创建对象,因此它是在堆内存中创建的,而堆栈内存中包含该对象的引用。
当我们在第四行中创建Memory对象时,也会发生类似的过程。现在,当我们在第5行调用foo()方法时,将在堆栈顶部创建一个块,以供foo()方法使用。
由于Java是按值传递的,因此在第六行的foo()堆栈块中创建了对Object的新引用。在第7行创建一个字符串,该字符串进入堆空间的"字符串池",并在foo()堆栈空间中为其创建引用。
foo()方法在第8行终止,这时为堆栈中的foo()分配的内存块变为可用。
在第9行中,main()方法终止,并且为main()方法创建的堆栈存储器被销毁。
而且,程序在此行结束,因此Java Runtime释放了所有内存并结束了程序的执行。
Java堆空间和堆栈内存之间的区别
根据以上解释,我们可以轻松得出Heap和Stack存储器之间的以下差异。
堆内存由应用程序的所有部分使用,而堆栈内存仅由一个执行线程使用。
每当创建对象时,它始终存储在堆空间中,并且堆栈存储器包含对该对象的引用。
堆栈内存仅包含局部基本变量和堆空间中对象的引用变量。堆中存储的对象可全局访问,而其他线程则无法访问堆栈内存。
堆栈中的内存管理以LIFO方式完成,而在堆内存中则更为复杂,因为它在全球范围内使用。
堆内存分为Young-Generation,Old-Generation等,有关详细信息,请参见Java Garbage Collection。堆栈内存是短暂的,而堆内存是从应用程序执行的开始一直到结束。
我们可以使用-Xms和-Xmx JVM选项来定义启动大小和堆内存的最大大小。
我们可以使用-Xss定义堆栈内存大小。当堆栈内存已满时,Java运行时将抛出" java.lang.StackOverFlowError",而如果堆栈内存已满,则将抛出" java.lang.OutOfMemoryError:Java Heap Space"错误。
与堆内存相比,堆栈内存的大小要小得多。
由于内存分配(LIFO)的简单性,与堆内存相比,堆栈内存非常快。