前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再谈方法调用与堆栈

再谈方法调用与堆栈

作者头像
我是攻城师
发布2018-12-14 15:11:47
5710
发布2018-12-14 15:11:47
举报
文章被收录于专栏:我是攻城师我是攻城师

在JVM里面,最重要的两个运行时数据区,无非就是堆和栈了。

关于堆

堆内存是被多个线程共享的,而栈内存是线程私有的。堆主要用来存储运行时所有的对象数据和各种数组,简单点说通过new创建的实例,都会在堆上分配空间。堆在虚拟机启动时创建,并且堆具有自动垃圾回收的功能,在Java的世界里,程序员是没办法直接销毁你所创建的对象的,一切必须由GC垃圾回收器来完成,也就是你用完后的对象,并不是立即销毁的,而是在下一次gc发生时来完成回收的,堆的内存可以是固定的,也可以动态增长,并且不要求在内存里面是必须连续的,如果计算需要更多的内存,超过了当前有效的内存,那么就会抛出OutOfMemoryError异常。

堆里面还分配了一部分内存用于:

(1)方法区:

主要用来存储我们编译后的代码,包括每个类的结构,字段,方法数据,常量池等,如果内存不足也会发生OutOfMemoryError异常。

(2)运行时常量池

这个其实是方法区里面划分的一个区域,主要用来存储每个类或者接口里面的常量池表,包括我们熟悉的字符串常量池等。如果内存不足也会发生OutOfMemoryError异常

(3)本地的方法栈

为了支持native方法而存在的一部分区域,本地方法栈与虚拟机栈一样,也是线程私有的,发生的异常包括StackOverflowError和OutOfMemoryError。

关于栈

栈主要分虚拟机栈和本地方法栈,我们这里仅仅关注虚拟机栈。

我们先来看下Oracle文档的官网解释:

Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.

简单的说,栈属于线程私有的,每一个线程都有一个自己的栈,栈里面可以存储数据,这个待会细说。此外还负责方法的调用和返回,java的栈仅仅负责 压栈和出栈,栈内存本身是可以从堆上分配出来的,并且栈内存可以是不连续的。如果线程计算需要一个更大的栈超过了允许的值,就会抛出StackOverflowError异常,如果栈内存还允许动态增加,那么当下一次申请的内存,不满足当前的需要,就会抛出OutOfMemoryError异常。

前面说过栈可以存储数据,这其实是在栈帧(frame)里面完成的,主要存储local变量,也执行动态链接,给方法返回值,还负责分发异常。栈帧与方法与一对一的关系,也就是说,每次虚拟机调用一个方法时,就会生成一个frame,无论是否发生异常,当方法调用完成后总是销毁,正在执行的方法,其frame称为当前栈帧,当前栈帧执行完成会后,就会抛弃,然后继续调用下一个方法的栈帧,此时该栈帧就会变成当前栈帧,直到所有的栈帧执行完毕,程序才运行结束。对一个类的一个方法,在调用时对应一个栈帧,栈帧包含三部分内容:

(1)方法本身的local变量数组

单个local变量的值类型,包括boolean, byte, char, short, int, float, reference, 和 returnAddress,两个local变量可以存储long和double类型的值,注意这些都是定长类型,也就是说在方法里面声明上面提到的类型,其存储可以直接在栈上,但同样的类型如果是成员变量,那么存储就在堆上,这一点需要注意,另外栈上存储的是定长,像字符串(底层是char数组),各种对象实例,数据本身都是存储在堆上,栈里面仅仅存储 指针,也叫内存地址。

(2)方法里面的操作符栈

每个栈帧里面还包含一个后进先出的操作符栈(operand stack),这个主要是进行一些算术运算操作的,比如遇到的加减乘除等操作符等。

(3)当前方法运行时常量池的的引用

这里面主要是一些运行时常量池的引用,用于支持方法代码的动态链接。动态链接主要转变符号链接为真实的链接。

说了这么多,我们总结一下栈的特点:

首先是线程私有的,不同的线程拥有不同的栈,栈里面的数据,相互之前是不可见的。栈里面可以直接存储基本类型的数据,此外包括指针的内存地址,及方法的返回值,这些数据的内存分配都是在栈上,这也是我们为什么说方法里面的local变量是线程安全的原因,因为是线程私有,不涉及多线程的问题。栈里面包含了很多帧,在程序运行时的每个方法,都会生成一个帧入栈,执行的过程就是出栈的过程。如果栈里面引用了成员变量或者其他共享的变量,这个时候需要注意线程安全问题,因为这些变量是存储在堆上的。

最后我们来看下,堆和栈的图示:

一个分析的例子

下面,我们通过一个例子,来简单看下,方法在栈里面是如何执行的:

代码语言:javascript
复制
public class StackCallDemo {


    static class Cat{
        public String name;
    }

    public void m1(){
        int x=20;
        m2(x);// call m2 method
    }


    public void m2(int x){
        boolean c;
        m3();//call m3 method
    }


    public void m3(){
        Cat cat=new Cat();
        //more code
    }


    public static void main(String[] args) {


        StackCallDemo stackDemo=new StackCallDemo();
        stackDemo.m1();

    }


}

这个类代码非常简单,方法执行逻辑 main=> m1=> m2=> m3,注意这是调用顺序,也是入栈顺序,出栈顺序,也就是真正的执行顺序,刚好相反,图示如下:

注意每个出栈执行完的方法,就相当于销毁了,在堆里面的Cat对象,如果方法不再引用,那么就再次gc时,会被回收掉。通过上图,我们可以清晰的看到嵌套方法执行过程,想清楚这一点,我们再去理解递归方法就容易多了,如果你按照嵌套的方式,去思考递归,那肯定理解不了,但是我们按照栈的逻辑,去理解递归,就会发现容易多了,这里没有嵌套,只有顺序入栈,出栈,分别对应递和归。

总结:

本文主要介绍了Java里面堆和栈在运行时的数据区域和功能,并在文末结合了一个例子来演示了Java程序方法是如何执行的,了解方法的执行逻辑,有助于我们理解其工作原理,从而可以让我们更好的去分析一些复杂的方法逻辑或者算法,比如递归等。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 我是攻城师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于堆
  • 关于栈
  • 一个分析的例子
  • 总结:
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档