在使用Java开发时,面向对象是重点和难点,而要理解面向对象的问题,最重要的还是要搞清楚其在内存中的原理和内存图,本文记录了Java对象在内存中的情况,包括this,基本数据类型和引用数据类型以及局部变量和成员变量的原理。
目录
JVM虚拟机在整个计算机中占用一块内存,并且在内存中分为五个部分来各司其职:
重点是栈、堆和方法区:
当程序运行一个类,其字节码文件就会加载到方法区进行存储,方法区原本是和堆空间在一起,而从JDK8开始,取消方法区,新增元空间。把原来方法区的多种功能进行拆分,有的功能放到了堆中,有的功能放到了元空间中。如今,加载字节码的功能归给元空间了。
当运行一个类时,这个类的字节码文件就会加载到方法区中临时存储。当方法被调用就要进栈,执行完就要出栈,而new出来的东西都会在堆内存。
创建一个对象,要经历以下7个步骤
比如说运行代码student s = new student() ,图中所做的事是:在这里呢有一个很简单的student类,这个里面有两个属性,一个是name,一个是age,在这个里面还有一个study()的成员方法。我们在测试类当中需要创建对象,测试类的名字叫做test student,首先创建了它的对象,然后打印s,再用s调用其中的name跟age并进行打印,在对name跟age进行了赋值,赋完值之后再获取并打印,最后调用study()。
内存会做这几件事件事。
因此之后如果调用方法println(s)就会打印储存的地址值,println(s.name,s.age)就打印地址值对应的空间内的值。再赋值也是把"阿强" 和 23赋值给堆内存中成员变量。如果调用成员方法s.study()就会由堆内存储存的地址值再找到方法区对应的方法,并加载进栈内存。
study()和main()执行完便会出栈,main()中的变量也会消失,而没有变量指向的堆内存空间也会被消失,也就是清除了。
同理,如果要处理不止一个对象,那么也来举个例子,比如说有两个对象,只要出现new,就说明要创建一个对象,在堆空间内开辟块空间,创建几个就开辟几块,且相互独立。
首先类加载进方法区。同上一样创建第一个对象student s1=new student(),接着打印对象,对age和name进行赋值,赋完值之后获取name,age,再用第一个对象去调用study()。接着创建了第二个对象进行了一遍相同的操作。其实很简单,就是把刚刚的一个对象重复了两次而已。
内存中的步骤也是:
第二次创建对象class文件是否还要再加载一次?答案是不需要,因为class文件已经加载过了。所以说我第二次在创建对象的时候,class文件就不需要再加载了,直接用就可以了。
在s2的study()也调用完出栈后,没有可执行的语句,所以main()方法出栈,变量s1和s2也没有了,所以堆空间内的也会销毁。
还是使用与上面相同的例子,但在这次第二个对象并没有new出来,而是把stu1这个变量里面记录的东西赋值给了stu2。在内存当中是这样的,首先会去声明一个stu2的小空间,这个空间也能存储student这个类对象的地址值,那你说这个空间里面存的是stu1这个里面记录的001赋值给了stu2,stu2记录的其实也就是001,也能通过001也能找到堆内存的空间,相当于就是两个变量都指向了同一个对象。
之后运行stu1=null 和 stu2=null就会分别让两个变量变为空指针,无法再打印堆内存储的值了。
比如method()中第一个打印方法,会触发就近原则,便会打印局部变量的age,但要想使用成员变量的age就应在前面加上this. 那么this在内存中的原理可以从下图中看到
堆内存创建了对象,把001这个地址值赋值给栈中左边的变量s,method()是被s调用的,所以说方法里面记录的调用者的地址值就是001,那么this记录的也是001,这就是this的本质,它就是代表方法调用者的地址值。当前的方法是s调用的,s和this代表的都是地址值001。
基本数据类型
引用数据类型:除了上边的其他所有类型。
它们的本质区别在于:基本数据类型就是在内存空间储存真实的数据值,真实的存在栈中而与其他的空间没有关系。
而在代码中创建的对象都是引用数据类型,栈中存储的只是一个地址值,而对象真实的值是存储在堆内存中的。因此引用就可以理解为使用其他空间中存储的值。同理数组也是,栈内存储数组的地址值,真实值则是在堆内存中存储。
成员变量:类中方法外的变量
局部变量:方法中的变量
具体区别如下表
在内存当中,可以看到变量a在栈内的方法中,而name、age都是在堆内存为对象开辟的空间中。方法出栈,变量a自然也就销毁。而没有方法出栈,对象不再被调用,自然堆内存中存储的name和age也就都被销毁了。