堆和栈的概念接触已久,也很容易让人似懂非懂。本文阐述它们的区别和作用。配合一个小例子,加深对其理解。
堆内存
堆内存是在Java程序运行时分配的,它用来存放对象,对象也总是在堆中。GC的作用域也是在堆内存上,它回收那些空引用对象。堆上的对象可以被程序全局应用到。
栈内存
栈内存是被执行线程所用的,它用来存放引用,这些引用指向堆内存上的对象。栈内存的分配依赖方法调用,当一个方法被调用到,此时一块内存区域就被分配,它用来存放方法内部声明的一些基本数据类型(int,boolean...)和指向堆内存的引用。方法一旦执行完毕,存放的内容也就被清空了。栈还有两个显著的特点:
- 顺序。栈遵循后进先出的规则
- 相对于堆内存,它被分配的空间很小。
以上就介绍完了堆内存和栈内存,收工!
等等,Linus说
Talk is cheap. Show me the code
实例分析
还是瞅瞅代码吧。
package com.azhengye.test;
public class Test {
public static void main(String[] args) {
Person yPerson = new Person("5");
Person oPerson = new Person("100");
swapAge(yPerson, oPerson);//能成功交换年龄吗?
System.out.println("yPerson.age=" + yPerson.getAge());
System.out.println("oPerson.age=" + oPerson.getAge());
changeAge(oPerson);//更改后的年龄是多少了?
System.out.println("oPerson.age=" + oPerson.getAge());
}
private static void swapAge(Person p1, Person p2) {
Person temp = p1;
p1 = p2;
p2 = temp;
}
private static void changeAge(Person person) {
person.setAge("5");
person = new Person("30");
person.setAge("40");
}
static class Person {
private String age;
public Person(String age) {
this.age = age;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
}
======我是答案分割线,先别往下看,自己想下结果======
<br />
<br />
<br />
<br />
<br />
答案揭晓,我们看看运行结果。
运行结果
我们看到交换年龄没有成功,而更改年龄却成功了。
下面分析下这段简单代码执行时发生的故事。
1.程序执行,JVM会把Test类加载到堆内存中,程序的入口在main方法,既然是方法,JVM就会创建出一个栈,然后将main方法压入栈中。
2.main方法中创建了两个Person对象,这两个对象存放在堆内存上,JVM如何找到它们呢?这个时候就栈中创建了两个引用yPerson/oPerson分别指向堆上的Person对象。
3.接着执行swapAge方法,在swapAge方法区域,创建temp引用交换p1和p2的指向,如果我们添加上log可以看到这个时候p1和p2可以交换成功。
private static void swapAge(Person p1, Person p2) {
System.out.println("before: in swapAge p1 is yPerson p1.age=" + p1.getAge());
System.out.println("before: in swapAge p2 is oPerson p2.age=" + p2.getAge());
Person temp = p1;
p1 = p2;
p2 = temp;
System.out.println("after: in swapAge p1 is yPerson p1.age=" + p1.getAge());
System.out.println("after: in swapAge p2 is oPerson p2.age=" + p2.getAge());
}
看看结果:
添加log后swapAge执行结果
4.swapAge方法执行完毕,关键的点来了,swapAge会被弹出栈,这也就意味着我们在swapAge方法内的交换操作也结束了。它不会在影响main方法区域的引用指向。
5.swapAge弹出栈后,此时栈里只剩下main方法区域了,接着执行打印log的语句:
System.out.println("yPerson.age=" + yPerson.getAge());
System.out.println("oPerson.age=" + oPerson.getAge());
注意了这里的yPerson/oPerson都是在main方法区域分配的,swapAge方法区域已久随着出栈操作被释放掉了,因此swapAge方法的交换根本不会影响yPerson/oPerson的指向。所以呈现的结果就是交换失败。
6.程序开始执行changeAge方法,同样的会为changeAge方法分配一块区域,并将该区域压入栈中。
7.changeAge方法接收的是一个指向Person("100")的引用,person.setAge("5")执行后,这时Person("100")这个在堆上的对象改变了,其age值变成了"5"。
8.接着执行
person = new Person("30");
person.setAge("40");
person先是重新被指向了堆内存上新生成的对象Person("30"),接着对该对象age做更改,将其改为"40"。
9.changeAge方法执行完毕,同样被弹出栈,随着出栈操作,changeAge方法里创建的新对象Person("30")就没有引用指向它了,在GC运行时就可能会被回收掉。当前栈里又只剩下了main方法区域。这个时候打印oPerson的对象age值,oPerson在main方法区域的指向一直没有被修改,但在changeAge方法执行时改变了oPerson所指向对象的值,因此oPerson.age值变成了"5"。
10.最后程序执行完毕main方法区域也被出栈,栈空间被释放,堆空间的对象也没有引用指向它们,同样被释放掉。程序到此结束。
上述过程中changeAge方法执行时的内存示意图如下。
changeAge方法执行时内存示意图
Stack和Heap区别
结合以上示例,最后总结下Stack和Heap的区别。
- 堆内存可以被整个程序访问到,而栈内存在执行时分配,只能被执行线程访问到。
- 对象在堆内存上分配,指向对象的引用在栈内存上,同时基础数据类型也是存放在栈内存。
- 栈内存分配内存块遵循后进先出原则,且所占空间小。堆内存上对象存放更复杂,有可能被分配到新生域(Young-Generation)或者年老域(Old-Generation)上。
- 栈内存上的分配的空间生命周期很短,方法执行完毕就不释放掉。
- 可以用-Xms 和 -Xmx等设置项配置堆内存大小。可以用-Xss配置栈内存大小。
- 栈内存溢出会抛出ava.lang.StackOverFlowError 。堆内存溢出则会抛出java.lang.OutOfMemoryError。
网友评论