栈
- 线程私有,不共享生命周期与线程相同。
- 每个方法在执行的同时都会创建一个栈帧,用于存放局部变量
- 每个方法从调用到执行完成,都对应一个在虚拟机栈中入栈到出栈的过程
- 内存空间连续,速度快
堆
- 所有线程共享
- 所有对象实例以及数组都在堆在分配
- 内存空间不连续,速度慢,但是灵活
局部变量和成员变量的分配
局部变量
- 引用类型:变量名(即地址)存放在栈,实例存放在堆。地址值在栈,所以随着方法结束,系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因。
- 基本数据类型:变量名和值都存放在栈
成员变量
- 引用类型:其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象都存储在相应的堆中
- 基本数据类型:变量名及其值放在堆内存中的。
- 可以这么想,栈是线程私有,而成员变量是所有线程共享,所有成员变量不会存放在堆中。
基本数据类型:
8位:Byte(字节型) 16位:short(短整型)、char(字符型) 32位:int(整型)、float(单精度型/浮点型) 64位:long(长整型)、double(双精度型) 最后一个:boolean(布尔类型)
异同
- 引用类型的实例,无论是成员变量还是局部变量,都是存放在堆
- 成员变量引用的地址值跟随实例存放在堆,局部变量,引用类型,引用地址存放在栈
- 对于基本数据类型,局部变量地址和值都是是存放在栈,成员变量地址和值是跟随实例都存放在堆
注:
-
当对象new出来,实体存在于堆,对象的成员变量已经在堆上分配空间,但对象里面的方法是没有出现的,只出现方法的声明,方法里面的局部变量并没有创建。等到对象调用此方法时,为了加快运行的速度,方法中的局部变量才会在栈中创建,所以,方法中的局部变量是在栈内的。
-
当然需要注意的是类变量(静态变量)存在于方法区!!引用类型的局部变量声明在栈,存储在堆
方法区
- 各个线程共享区域,存储.class文件、常量、静态变量、即时编译器编译后的代码等数据、运行时常量池
- 虚拟机描把方法区描述为堆的一个逻辑部分,有一个别名Non-Heap(非堆)
- 静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
- 而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
String类型
- String a = “xxx”;
在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间。然后在栈中开辟一块空间,命名为“s1”,存放的值为常量池中“myString”的内存地址 - String b = new String(“xxx”);
在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间,保证常量池中只有一个“myString”常量,节省内存空间。然后在内存堆中开辟一块空间存放new出来的String实例,在栈中开辟一块空间,命名为“s1”,存放的值为堆中String实例的内存地址,这个过程就是将引用s1指向new出来的String实例
程序计数器
- 一块较小的内存空间,线程私有,不共享
- 可以看做当前线程所执行的字节码的行号指示器。字节码解释器通过改变这个计数器的值,来选取下一条字节码指令,分支、循环、跳转、异常处理等都依赖这个计数器
- 线程私有,在任何一个确定时刻,一个处理器都只会执行一条线程中的指令,每条线程都有独立的程序计数器,互补影响独立存储
- 如果线程正在执行一个java方法,这个计数器记录的是正在执行的字节码指令的地址如果执行的是native方法,计数器值为空。
- 此内存区域是唯一一个在java虚拟机中没有规定任何OutMemoryError的区域
本地方法栈
- 本地方法栈和虚拟机栈相似,区别就是虚拟机为虚拟机栈执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native方法服务。本地方法栈中使用的语言,使用方式,数据结构没有强制要求。由具体虚拟机自由实现它