Java内存分配---堆与栈

堆和栈的概念接触已久,也很容易让人似懂非懂。本文阐述它们的区别和作用。配合一个小例子,加深对其理解。

堆内存

堆内存是在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;
        }
    }
}

==========我是答案分割线,先别往下看,自己想下结果==========










答案揭晓,我们看看运行结果。
这里写图片描述
我们看到交换年龄没有成功,而更改年龄却成功了。
下面分析下这段简单代码执行时发生的故事。
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。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值