总结:
1 栈:为编译器自动分配和释放,如函数参数、局部变量、临时变量等等
2 堆:为成员分配和释放,由程序员自己申请、自己释放。否则发生内存泄露。典型为使用new申请的堆内容。
除了这两部分,还有一部分是:
3 静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。
Java堆内存
堆内存在Java运行时被使用来为对象和JRE类分配内存。不论什么时候我们创建了对象,它将一直会在堆内存上创建。垃圾回收运行在堆内存上来释放没有任何引用的对象所占的内存,任何在堆上被创建的对象都有一个全局的访问,并且可以在应用的任何位置被引用。
Java栈内存
Java的栈内存被用来线程的执行,他们包含生命周期很短的具体值的方法和在堆中使用这个方法对象的引用。栈内存是LIFO(后进先出)序列。当方法被调用的时候,堆内存中一个新的块被创建,保存了本地原始值和在方法中对其他对象的引用。这个方法结束之后,这个块对其他方法就变成可用的了。栈内存与堆内存相比是非常小的。
我们用下边的例子理解堆内存和栈内存
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
}
下边的图片展示了上边程序堆和栈内存的引用,并且是怎么用来存储原始值、对象和变量的引用。
我们来看看程序执行的过程:
1、只要我们一运行这个程序,它会加载所有的运行类到堆内存中去,当在第一行找到main()方法的时候,Java创建可以被main()方法线程使用的栈内存。
2、当在第一行,我们创建了本地原始变量,它在main()的栈中创建和保存。
3、因为我们在第三行创建了对象,它在堆内存中被创建,在栈内存中保存了它的引用,同样的过程也发生在第四行我们创建Memory对象的时候。
4、当在第五行我们调用foo()方法的时候,在堆的顶部创建了一个块来被foo()方法使用,因为Java是值传递的,在第六行一个新的对象的引用在foo()方法中的栈中被创建
5、在第七行一个String被创建,它在堆空间中的String池中运行,并且它的引用也在foo()方法的栈空间中被创建
6、foo()方法在第八行结束,此时在堆中为foo()方法分配的内存块可以被释放
7、在第九行,main()方法结束,栈为main()方法创建的内存空间可以被销毁。同样程序也在行结束,Java释放了所有的内存,结束了程序的运行
堆内存和栈内存的区别
基于上边的解释我们可以很简单的总结出堆和栈的区别:
1、应用程序所有的部分都使用堆内存,然后栈内存通过一个线程运行来使用。
2、不论对象什么时候创建,他都会存储在堆内存中,栈内存包含它的引用。栈内存只包含原始值变量好和堆中对象变量的引用。
3、存储在堆中的对象是全局可以被访问的,然而栈内存不能被其他线程所访问。
4、栈中的内存管理使用LIFO的方式完成,而堆内存的管理要更复杂了,因为它是全局被访问的。堆内存被分为,年轻一代,老一代等等
5、栈内存是生命周期很短的,然而堆内存的生命周期从程序的运行开始到运行结束。
6、我们可以使用-Xms和-Xmx JVM选项定义开始的大小和堆内存的最大值,我们可以使用-Xss定义栈的大小
7、当栈内存满的时候,Java抛出java.lang.StackOverFlowError异常而堆内存满的时候抛出java.lang.OutOfMemoryError: Java Heap Space错误
8、和堆内存比,栈内存要小的多,因为明确使用了内存分配规则(LIFO),和堆内存相比栈内存非常快。