Java内存分配---堆与栈

作者: 小草凡 | 来源:发表于2016-11-29 23:57 被阅读130次

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

    堆内存

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

    相关文章

      网友评论

      • 半夏风痕:程序执行,JVM会把Test类加载到堆内存中。这个对吗?
      • 小草凡:Jinfo看下设置的值生效没,jmap在看看内存使用情况。如果业务场景已经退出了,但内存使用没有跟着显著下降,大概率存在泄漏,可以用mat 看下dump信息
        半夏风痕:嗯嗯,多谢指教。问题已解决了,我只关注RES常驻内存,忽略虚拟内存。
      • 半夏风痕:使用-Xms和-Xmx来指定JVM堆空间的初始值和最大值都为6144M,JAVA进程起来后,用top命令查看内存,该进程实际上只占用了2.9多个G的内存。但跑一段时间的业务,内存占用增加了5个多G。请问楼主,这个初始值为什么没生效?这种现象是出现内存泄露了吗?

      本文标题:Java内存分配---堆与栈

      本文链接:https://www.haomeiwen.com/subject/pevzpttx.html