堆和栈的概念接触已久,也很容易让人似懂非懂。本文阐述它们的区别和作用。配合一个小例子,加深对其理解。
堆内存
堆内存是在 Java 程序运行时分配的,它用来存放对象,对象也总是在堆中。GC 的作用域也是在堆内存上,它回收那些空引用对象。堆上的对象可以被程序全局应用到。
栈内存
栈内存是被执行线程所用的,它用来存放引用,这些引用指向堆内存上的对象。栈内存的分配依赖方法调用,当一个方法被调用到,此时一块内存区域就被分配,它用来存放方法内部声明的一些基本数据类型 (int,boolean…) 和指向堆内存的引用。方法一旦执行完毕,存放的内容也就被清空了。栈还有两个显著的特点:
顺序。栈遵循后进先出的规则 相对于堆内存,它被分配的空间很小。
以上就介绍完了堆内存和栈内存,收工!
等等, Linus 说
实例分析
还是瞅瞅代码吧。
- 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; } }}
==========我是答案分割线,先别往下看,自己想下结果==========
答案揭晓,我们看看运行结果。
我们看到交换年龄没有成功,而更改年龄却成功了。
下面分析下这段简单代码执行时发生的故事。
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());
- }
看看结果:
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 方法执行时的内存示意图如下。
Stack 和 Heap 区别
结合以上示例,最后总结下 Stack 和 Heap 的区别。
堆内存可以被整个程序访问到,而栈内存在执行时分配,只能被执行线程访问到。 对象在堆内存上分配,指向对象的引用在栈内存上,同时基础数据类型也是存放在栈内存。 栈内存分配内存块遵循后进先出原则,且所占空间小。堆内存上对象存放更复杂,有可能被分配到新生域 (Young-Generation) 或者年老域 (Old-Generation) 上。 栈内存上的分配的空间生命周期很短,方法执行完毕就不释放掉。 可以用 - Xms 和 -Xmx 等设置项配置堆内存大小。可以用 - Xss 配置栈内存大小。 栈内存溢出会抛出 ava.lang.StackOverFlowError 。堆内存溢出则会抛出 java.lang.OutOfMemoryError。就爱阅读 www.92to.com 网友整理上传, 为您提供最全的知识大全, 期待您的分享,转载请注明出处。
来源: http://www.92to.com/bangong/2016/12-02/13841372.html