【JavaSE】Java内存模型
JVM
分为3个部分:栈,堆,方法区。
方法区:类加载信息,常量,静态变量
堆:放对象的地方。在JVM启动时创建。这里对象有类属性但没有类里卖弄的哪些方法。
栈:放基本数据类型。每个线程一个栈,负责方法调用存储一些局部变量,方法参数返回值什么的。方法被调用的时候就有一个栈帧,这里面放说的那些数据。然后调用方法,栈帧入栈,结束调用就出栈。
无论是普通类型的变量还是引用类型的变量(俗称实例),都可以作为局部变量,他们都可以出现在栈中。
只不过普通类型的变量在栈中直接保存它所对应的值,而引用类型的变量保存的是一个指向堆区的指针,通过这个指针,就可以找到这个实例在堆区对应的对象。
因此,普通类型变量只在栈区占用一块内存,而引用类型变量要在栈区和堆区各占一块内存。
什么是实例,什么是对象?
Object o = new Object();
左边的是实例,在栈中。
右边的是对象在堆中。
操作实例实际上是通过实例的指针间接操作对象。多个实例可以指向同一个对象。
栈中的数据和堆中的数据销毁并不是同步的。
方法一旦结束,栈中的局部变量立即销毁,但是堆中对象不一定销毁。
因为可能有其他变量也指向了这个对象,直到栈中没有变量指向堆中的对象时,它才销毁。
而且还不是马上销毁,要等垃圾回收扫描时才可以被销毁。
每一个应用程序都对应唯一的一个JVM实例,每一个JVM实例都有自己的内存区域,
类的成员变量在不同对象中各不相同,都有自己的存储空间(成员变量在堆中的对象中)。而类的方法却是该类的所有对象共享的,只有一套。
对象使用方法的时候方法才被压入栈,方法不使用则不占用内存。
栈有一个很重要的特性:栈中的数据可以共享。
当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。
常量池技术
基础数据类型和基础数据类型的包装类
1 | public class test { |
i和i0均是普通类型(int)的变量,所以数据直接存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。
1和i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer包装类实现了常量池技术,因此i1、i2的40均是从常量池中获取的,均指向同一个地址,因此i1=12。
****4.i****4和i5均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i4和i5不相等,因为他们所存指针不同,所指向对象不同。
1和d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1和d2存放的指针不同,指向的对象不同,所以不相等。
以上提到的几种基本类型包装类均实现了常量池技术,但他们维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1和i2就不相等了。
String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。
创建对象内存分配流程
- 方法区:加载类信息。
- 堆:初始化对象。(默认初始化,显式初始化,构造器初始化)
- 栈:把队中对象的地址赋给栈中变量名。