前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小变量大学问 干了这杯全家桶

小变量大学问 干了这杯全家桶

作者头像
用户5745563
发布2019-07-03 17:29:57
3130
发布2019-07-03 17:29:57
举报
文章被收录于专栏:码思客码思客
java零基础入门-面向对象篇(七) 各种变量在JVM中的位置和运行方式

本章对前面学的知识进行一个总结,为下面要介绍的封装继承和多态打好基础。

学习就要不断的复习和总结,才能让学过的知识不断的得到沉淀,变成自己的知识,切勿心浮气躁,囫囵吞枣。

在我的编程生涯中,遇到过很多问题和BUG,最让我难忘的就是那种既不报错,又没有异常的BUG,这种BUG解起来让人痛苦不堪,比如我当年就碰到过一个由static 静态变量引发的BUG,最后只能用输出语句排查,这就是典型的知识点掌握的不牢引发的问题。当然那段代码不是我写的。

所以在我们使用变量的时候,一定要掌握一些原则,就是潜规则,不能随便看心情来定义变量。如果能够知道变量的内在原理,那对我们写代码会有更大的帮助。

变量的分类

根据变量的声明位置一般分为两种

在类里面方法外面声明的变量,是成员变量。成员变量又分为类变量和实例变量。类变量就是static修饰的变量,它属于类。实例变量就是没有static修饰的变量,它属于对象。

再就是在方法内部声明的变量,是局部变量。局部变量包括方法的参数,方法内部定义的变量。

变量分类

1.普通成员变量,属于对象

2.静态成员变量,属于类

3.局部变量,方法的参数是局部变量

4.方法内部定义的变量是局部变量

前面说堆栈的时候,说变量在栈里面,其实那仅仅是指局部变量。普通成员变量在堆里面,静态成员变量在方法区。是不是又有点懵,就一个变量整这么多地方干啥,东放一点西放一点,放一起不就完了。其实分开存放是有原因的,因为他们的生命周期不同,不同的变量有着不同的生命周期,导致他们必须存放在不同的地方。

为什么生命周期不同要放不同的地方?你想想你家有冰箱吧,你试试不把冰淇淋放冰箱,估计你还没吃,它就变成液体了。因为冰淇淋的生命周期不同,它不在冰箱活不了多久,要根据它的特点指定存放的地方,不然你还没开始调用(吃),他就被销毁了(化了)。

栈帧

首先来看局部变量,局部变量是生命周期最短的,为什么呢?因为局部变量一般定义在方法里面,他随着方法的调用创建,随着方法的完毕销毁。

我们前面说的栈是一种数据结构,它是一种设计概念,而我们虚拟机里面的虚拟机栈是用这个设计概念实现的程序,请不要混淆两者的概念。

现在我们可以完善一下前面讲的那个栈的结构了。在JVM中,我们每次入栈其实不是入栈的一个变量,而是一个栈帧,在我们知识体系不完善的时候用入栈变量来解释可能会好理解一些,但是现在我们储备了足够的知识,我们就可以理解的更准确一点。

栈帧是什么?栈帧是虚拟机进行方法调用和方法执行的数据结构。就是说,我们每一个方法,都对应了一个栈帧。

栈帧主要由以下几个部分组成,局部变量表,操作数栈,动态连接,方法返回地址。这里我们只关注局部变量表,其他几个部分,知道有这个东西就行了。

栈帧的组成

对我们来说,栈帧里面最需要了解的就是局部变量表。我们方法中定义的局部变量,就是参数和方法内定义的变量都存在这里(第一张图里面,3,4)。下面我们来看看调用方法的时候,栈帧是怎么进出的。

1.当方法执行到 studyFrameWork 这个方法的时候,栈帧入栈。方法的局部变量 frameWork 和 spring 在栈帧的局部变量表中,一起入栈。

1

2.当studyFrameWork这个方法执行完毕后,方法对应的栈帧出栈。

2

3.当程序继续运行到下一个方法 studyWebFrameWork 的时候,这个方法对应的栈帧入栈,并且这个方法的局部变量 webFrameWork 和 vue 保存在局部变量表,一同入栈。

3

以上这个类是将两个方法分开执行的情况,我们再看看下面这种情况

1.调用方法 studyAllFrameWork 的时候,局部变量 frameWork 和 spring 在局部变量表中入栈

1

2.这个时候,studyAllFrameWork 这个方法没有执行完毕,在这个方法内部又再次调用了 studyWebFrameWork 这个方法,局部变量vue,webFrameWork 在局部变量表中入栈。这个时候栈中有两个栈帧,先入栈的在栈底,后入栈的在栈顶。

2

3.后入栈的方法执行完毕,返回值返回给先入栈的方法,这个时候后入栈的方法出栈,程序继续执行。

3

4.最后当第一个入栈的方法也执行完毕后,第一个方法对应的栈帧也出栈了。

4

看到这里,很多同学心里的问题应该有答案了。

为什么只有栈顶可以操作?我直接用栈底的数据不行吗?

因为当我们调用方法的时候,方法中的数据按照顺序入栈,如果没有运行完毕或者出现异常,这个方法对应的栈帧是不会出栈的,如果往下执行,又调用了另外一个方法,那么另外一个方法对应的栈帧会继续入栈,在栈顶,然后程序开始执行后入栈的这个栈帧对应的方法,所以只有栈顶的这个栈帧对应的方法才可以操作,当他运行完毕出栈以后,会继续运行第一个没有运行完毕的方法,这时候,第一个方法对应的栈帧就是栈顶(因为只有一个栈帧了)。所以只有栈顶可以操作。

当然,如果我们第二个方法中又调用了第三个方法,就会有第三个方法对应的栈帧入栈。然后依次运行,出栈。

入栈的时候,局部变量开始生效,出栈的时候,局部变量就销毁掉了,所以局部变量的生命周期只在方法内部有效,出了方法就不能使用了。现在大家应该对这个有更深刻的理解了吧。

成员变量的位置

成员变量

成员变量有两种,普通成员变量属于对象,因为当我们使用 new 创建一个对象的时候,这个对象会在堆里面开辟一个空间,而成员变量会随着这个对象的创建,也在堆里面。而当程序里面没有任何变量指向这个对象的时候,这个对象就会被清洁阿姨回收掉,这个时候成员变量也会一起被回收掉。

所以普通成员变量的生命周期和对象一样,随着对象的创建而创建,随着对象的回收而回收。

再看另外一种,静态成员变量。静态成员变量在方法区,它属于类,所以它的生命周期跟类一样。也就是说当对象生老病死以后,这个静态成员变量依然活的好好的。关于类的加载需要更多的知识,我们后面学习更多的知识后再来详细解释。

本文开头说过一个静态变量引发的BUG就是,某位不知名程序员为了满足一个 “全局” 的需求,结果使用了静态变量,然后他每次 new出来的对象都会对这个全局变量进行修改, 由于方法区是线程共享的,结果出现了意料之外的结果。所以理解每种变量所在区域和该区域的特点很有必要。

对于各种变量,我们知道它们的生命周期和作用范围,对我们后面的学习很有必要。比如我们要控制变量的作用范围,可以使用局部变量的时候就使用局部变量,过多的使用成员变量会增加堆内存的开销。再就是我们讲到封装等概念的时候,也需要控制变量的作用范围,使我们的程序更加符合程序设计的规范。

最后,本章解释的jvm栈帧结构其实也不是很完全,但是作为初学者,学这些已经远远超出了我们需要掌握的知识,能理解最好,实在不能理解也没有关系。

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

本文分享自 码思客 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档