JAVASE进阶:内存原理剖析——数组、方法、对象、this关键字的内存原理

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:首期文章
📚订阅专栏:JAVASE进阶
希望文章对你们有所帮助

技术栈我已经基本上是学完了的,这段时间除了技术栈加班加点的学,还去接了别人的课设,接的最难的课设已经是涉及到了Redis、SpringCloud、ElasticSearch等。
除了大体上的后端开发技术栈我都学了,我还学了一些比较容易的比如mybatis-plus、nginx等等,但是都是速成的,暂时不做总结,为了将来的面试也是要去重新过一遍总结一遍的。
以前学习过的东西永远都是非常重要的基础:javase、Javaweb、MySQL,所以我会再去学一遍里面比较高级的用法、底层的原理,以及面试常考的题目。

Java内存分配

每个软件运行的时候都会占用内存,而Java运行的时候,jvm会占用一块内存空间,为了更好的应用这一部分空间,jvm将其分为了五个部分:

1、栈:方法运行时使用的内存
2、堆:存储对象或者数组,new来创建的,都存储在堆内存
3、方法区:存储可以运行的class文件
4、本地方法栈
5、寄存器

在jdk7以前,方法区实际上是和堆放在一起的,真实环境下也是连续的一段物理空间,但是这种设定方式并不是太好,这在以后慢慢剖析,不是本章重点。

到了jdk8以后,取消了方法区,新增元空间,把原来方法区的多种功能进行了拆分,有的功能放到了堆中,有的功能放到了元空间中。

主要需要掌握的就是栈和堆,通过简单代码引入一下:
在这里插入图片描述
这一串代码并没有new关键字,所以执行起来根本不要用到堆,只需要用到栈:
在这里插入图片描述

数组的内存图

在某个方法执行的时候,可能会有new关键字,创建一个数组,new就会用到堆空间了,例如下面代码:
在这里插入图片描述
其执行的底层如下:
在这里插入图片描述
总之,new关键字后面的数组创建是在堆内存进行的,而返回给栈内存中的是这个数组的地址,当需要访问这个数组中的元素时,指定地址即可。

需要注意的是,int[] arr2 = {33, 44, 55}这一行代码是数组的静态初始化,虽然没有new,但是这实际上是简写,完整代码是int[] arr2 = new int[]{33, 44, 55},所以也是包含new关键字的,数组的创建依旧要到堆空间中。

有一种情况,两个数组指向了同一个空间的内存图:

int[] arr1 = {11, 22};
int[] arr2 = arr1;

这时候,栈内存有arr1和arr2的声明,而数组{11, 22}在堆内存中只有一个,也就是说int[] arr2 = arr1这条语句只是把arr1地址赋值给arr2,都指向了堆内存中的同一个数组。因此当arr1数组的元素改变的时候,查询arr2的时候能够发现它改变了。有点像之前学的指针,挺简单的不用过多解释了。

方法的基本内存原理

方法的执行是在栈进行的,栈具有后进先出的特点。所以需要注意一下方法嵌套时候方法调用的基本内存原理,如下代码:
在这里插入图片描述
执行的流程应该为:

1、执行main(),将main()压入栈
2、执行main()中的eat(),将eat()压入栈
3、执行eat()中的study(),将study()压入栈
4、执行study()中的输出语句,执行完毕,study()出栈
5、执行eat()中的输出语句
6、执行eat()中的sleep(),将sleep()压入栈
7、执行sleep()中的输出语句,执行完毕,sleep()出栈
8、eat()方法随之执行完毕,eat()出栈
9、main()结束,main()出栈

学过数据结构很容易懂,不用画图讲了。

对象的内存图

之前讲到,jdk8以后不再将方法区和堆合在一起了,而是拆开成元空间和堆。
元空间:字节码文件加载时进入的内存(.class文件)。
当我们执行Student s = new Student()时,底层会进行下列操作:

1、加载class文件
2、声明局部变量(等式左边)
3、在堆内存中开辟一个空间(等式右边)
4、默认初始化
5、显示初始化
6、构造方法初始化
7、将堆内存的地址值赋值给左边的局部变量

其中4、5、6是对第3步中开辟的空间进行的赋值操作:

默认初始化是对内存元素的默认赋值(int默认为0,String默认为null)
显示初始化是将Student类中成员变量的值赋值到堆空间中(如果有值)
构造方法初始化是将new Student()里面的参数赋值给堆空间,但这里无参构造可以忽略

一个对象的内存图

执行下列语句:
在这里插入图片描述
执行流程如下:

1、将StudentTest.class字节码文件存入方法区(元空间),并将main()进行临时存储
2、虚拟机自动调用程序主入口main(),main()就被加载到里面
3、将Student.class字节码文件加载到方法区中进行临时存储,记录了这个类中的所有信息(成员变量、成员方法)
4、中声明局部变量Student s
5、内存中开辟了一个空间,创建对象Student(),并会将方法区中临时存储的信息拷贝一份放在中,并进行默认初始化、显示初始化、构造方法初始化。除此之外,还会保存成员方法的地址。
6、将内存的地址值赋值给内存的局部变量Student s
7、栈内存执行相应操作,直到执行到study()方法,栈内存先根据地址找到了堆内存中的对象,堆内存根据成员方法的地址找到方法区中临时存储的成员方法。找到后,study()进入栈执行完就出栈
8、程序全部执行完毕后,栈内存中方法消失,方法中定义的局部变量也消失,也没有人再去用堆内存中的对象了,这个对象就会变成垃圾被jvm回收(垃圾回收机制)。

在这里插入图片描述

多个对象的内存原理

在这里插入图片描述
可以自行分析一下运行的流程,需要注意的一点是:Student()被new两次,堆内存中会创建2次对象,但方法区中不会重复将Student.class进行加载

两个变量指向同一个对象的内存图

Studnet stu1 = new Student();
Student stu2 = stu1;

和上述不同的是,堆内存中的Student只有一个,Student stu2 = stu1只是在栈内存中将地址进行传递而已。

基础数据类型和引用数据类型

通过上面例子应该很容易理解什么叫做引用数据类型了,Student s = new Student()这条语句中,栈空间中s被赋值了个地址,而不是真正的值,真正的值是引用了堆内存中的值,即为引用数据类型。
int a = 10这条语句,值直接就在栈内存中进行创建,即为基础数据类型。

this的内存原理

由于javase内部是遵循就近原则的,所以当我们这么写set方法的时候会出现问题:

String name; //成员变量
public void setName(String name){
	name = name;
}

set方法里的name都是局部变量,这样的语句并不能将值赋给成员变量,这会出现问题,正确的赋值语句为this.name = name,这就是this关键字的作用:区分局部变量和成员变量。

而this之所以能有这种作用,是因为this的本质为所在方法调用者的地址值

例如Student s = new Student(),再Student中的成员方法,方法体内若有this关键字,这个this代表的就是s的地址值(方法调用者),这样就会正确指向了成员变量而不是局部变量。

  • 24
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

布布要成为最负责的男人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值