面向对象Java开发——对象的内存原理和内存图

在使用Java开发时,面向对象是重点和难点,而要理解面向对象的问题,最重要的还是要搞清楚其在内存中的原理和内存图,本文记录了Java对象在内存中的情况,包括this,基本数据类型和引用数据类型以及局部变量和成员变量的原理。

目录

预备知识

一、一个对象的内存图

二、多个对象的内存图

注意

三、两个引用指向同一个对象内存图

四、this的内存原理

五、基本数据类型和引用数据类型的区别

六、局部变量和成员变量的区别


预备知识

JVM虚拟机在整个计算机中占用一块内存,并且在内存中分为五个部分来各司其职:

重点是栈、堆和方法区:

当程序运行一个类,其字节码文件就会加载到方法区进行存储,方法区原本是和堆空间在一起,而从JDK8开始,取消方法区,新增元空间。把原来方法区的多种功能进行拆分,有的功能放到了堆中,有的功能放到了元空间中。如今,加载字节码的功能归给元空间了。

 当运行一个类时,这个类的字节码文件就会加载到方法区中临时存储。当方法被调用就要进栈,执行完就要出栈,而new出来的东西都会在堆内存。

 

 

一、一个对象的内存图

创建一个对象,要经历以下7个步骤

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

比如说运行代码student s = new student() ,图中所做的事是:在这里呢有一个很简单的student类,这个里面有两个属性,一个是name,一个是age,在这个里面还有一个study()的成员方法。我们在测试类当中需要创建对象,测试类的名字叫做test student,首先创建了它的对象,然后打印s,再用s调用其中的name跟age并进行打印,在对name跟age进行了赋值,赋完值之后再获取并打印,最后调用study()。

内存会做这几件事件事。

  • step1:先加载class文件,在方法区里面,会把student.class加载到其中临时存储,,它会有student这个类的所有信息,比如说所有的成员变量,成员方法。
  • step2:声明局部变量,就是在创建对象等号s的这个代码,栈内会开辟了一个空间,名字就叫做s,这个空间之后能存储student这个类对象的地址值。
  • step3:在堆内存当中去开辟了一个空间,也就是在创建对象等号右边new student(),那此时来看堆,只要有new,就是在堆里面去开辟了个小空间,而堆里面的这些空间是有地址值的,假设这块空间的地址值是001,它就会把student这个类里面所有的成员变量都拿过来拷贝一份,还会有所有成员方法的地址,存储方法的地址,为了以后用对象调方法的时候能找到对应的方法。
  • step4:看到图中堆内,name初始值为null,age初始值为0。
  • step5:比如如果是定义类的时候直接String name="xxx"; int age=23;便需要显示初始化,也就是如果有这一步,便会覆盖默认初始化的值
  • step6:构造方法初始化,看到在代码当中啊,小括号里面什么都没写,就表示现在调用的是空参构造,而空参构造里面也没有写代码,所以说构造方法初始化也可以忽略。但是如果用的是有参构造来创建对象,那么现在name和age就会有值了。
  • step7:把栈内存中分配的地址值001,赋值给栈内存中的局部变量s

因此之后如果调用方法println(s)就会打印储存的地址值,println(s.name,s.age)就打印地址值对应的空间内的值。再赋值也是把"阿强" 和 23赋值给堆内存中成员变量。如果调用成员方法s.study()就会由堆内存储存的地址值再找到方法区对应的方法,并加载进栈内存。

study()和main()执行完便会出栈,main()中的变量也会消失,而没有变量指向的堆内存空间也会被消失,也就是清除了。

二、多个对象的内存图

同理,如果要处理不止一个对象,那么也来举个例子,比如说有两个对象,只要出现new,就说明要创建一个对象,在堆空间内开辟块空间,创建几个就开辟几块,且相互独立。

首先类加载进方法区。同上一样创建第一个对象student s1=new student(),接着打印对象,对age和name进行赋值,赋完值之后获取name,age,再用第一个对象去调用study()。接着创建了第二个对象进行了一遍相同的操作。其实很简单,就是把刚刚的一个对象重复了两次而已。

内存中的步骤也是:

  1. 测试类的字节码文件要被加载到方法区当中,其main()方法也会临时存储
  2. 虚拟机要调用main()方法,所以main()进栈
  3. 执行第一行student s1=new student(),左边的s1声明空间,等号右边有new关键字就是在堆里面开辟了一个小空间,其内会有所有成员变量的值,所有的成员方法的地值引用。对于成员变量会有默认初始化,显示初始化,构造方法初始化,再会把一个空间的地址001赋值给了左边的S1,那么S1就可以通过001找到了右边的对象的这个空间
  4. name和age的默认初始值是null和0,方法中进行赋值为阿强和23
  5. 调用study()方法,则方法进栈,调用完后方法出栈
  6. 接着相同步骤创建s2

注意

第二次创建对象class文件是否还要再加载一次?答案是不需要,因为class文件已经加载过了。所以说我第二次在创建对象的时候,class文件就不需要再加载了,直接用就可以了。

在s2的study()也调用完出栈后,没有可执行的语句,所以main()方法出栈,变量s1和s2也没有了,所以堆空间内的也会销毁。

三、两个引用指向同一个对象内存图

还是使用与上面相同的例子,但在这次第二个对象并没有new出来,而是把stu1这个变量里面记录的东西赋值给了stu2。在内存当中是这样的,首先会去声明一个stu2的小空间,这个空间也能存储student这个类对象的地址值,那你说这个空间里面存的是stu1这个里面记录的001赋值给了stu2,stu2记录的其实也就是001,也能通过001也能找到堆内存的空间,相当于就是两个变量都指向了同一个对象。

之后运行stu1=null 和 stu2=null就会分别让两个变量变为空指针,无法再打印堆内存储的值了。

四、this的内存原理

比如method()中第一个打印方法,会触发就近原则,便会打印局部变量的age,但要想使用成员变量的age就应在前面加上this. 那么this在内存中的原理可以从下图中看到

堆内存创建了对象,把001这个地址值赋值给栈中左边的变量s,method()是被s调用的,所以说方法里面记录的调用者的地址值就是001,那么this记录的也是001,这就是this的本质,它就是代表方法调用者的地址值。当前的方法是s调用的,s和this代表的都是地址值001。


五、基本数据类型和引用数据类型的区别

基本数据类型

  • 整数类型
  • 浮点数类型
  • 布尔类型
  • 字符类型

引用数据类型:除了上边的其他所有类型。

它们的本质区别在于:基本数据类型就是在内存空间储存真实的数据值,真实的存在栈中而与其他的空间没有关系。

而在代码中创建的对象都是引用数据类型,栈中存储的只是一个地址值,而对象真实的值是存储在堆内存中的。因此引用就可以理解为使用其他空间中存储的值。同理数组也是,栈内存储数组的地址值,真实值则是在堆内存中存储。

六、局部变量和成员变量的区别

成员变量:类中方法外的变量

局部变量:方法中的变量

 具体区别如下表

在内存当中,可以看到变量a在栈内的方法中,而name、age都是在堆内存为对象开辟的空间中。方法出栈,变量a自然也就销毁。而没有方法出栈,对象不再被调用,自然堆内存中存储的name和age也就都被销毁了。

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

丹牛Daniel

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

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

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

打赏作者

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

抵扣说明:

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

余额充值