你想要什么?你在做什么?它们一样吗?
笔记
一、内存模型(运行时数据区)
虚拟机在执行程序时会把内存分为若干个部分
二、每个区域
1 程序计数器
可以看作是当前线程正在执行字节码的行号指示器,解释器就是通过修改程序计数器的值来选取下一条要执行的字节码指令的,是程序控制流的指示器。分支、循环、跳转、异常处理、线程恢复都是通过计数器实现的。
每个线程一个,线程销毁生命周期就结束了;可以让恢复执行的线程获得cpu时间片时知道自己执行的位置,从而可以从上次执行的位置继续执行。
如果线程执行的是java方法,程序计数器存储的是正在执行的字节码指令的地址。如果是native方法,存储的是空。
2 Java虚拟机栈
与线程生命周期相同。虚拟机栈描述的是java方法执行的内存模型。
2.1 栈帧
每个java方法执行时,都会创建一个栈帧,用来存储局部变量表、操作数栈、动态连接、方法返回地址等信息。
一个java方法从调用开始到结束的过程就是一个栈帧在虚拟机栈入栈出栈的过程。
2.2 局部变量表
局部变量表存放的是以下三种数据类型:
- 编译期可知的各种java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double);
- 对象引用(refrence类型,不等于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与次对象相关的位置);
- retturnAddress类型(新的虚拟机已经很少使用);
这些数据类型在局部变量表中的存储空间以局部变量槽(slot)表示,其中64位的long和double类型占2个变量槽,其他数据类型只占用1个。
局部变量表的大小(变量槽的数量而非具体空间的大小)在编译期间完成分配,具体一个变量槽占用多少内存根据不同的具体虚拟机实现而定。
如果线程请求的栈深度大于虚拟机栈允许深度,将会抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当栈扩展时无法申请到足够的内存时,会抛出OutOfMemoryError异常。HotSpot虚拟机的栈容量是不可以动态扩展的,如果在申请时失败也会报OOM异常。
在方法指向结束
2.3 操作数栈
局部变量表中的变量是不可直接使用的,如需使用必须通过相关指令将其加载至操作数栈中作为操作数使用。
2.4 动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,动态链接就是为了将这些引用转换为调用方法的直接引用。
2.5 方法返回地址
方法正常退出时,PC计数器的值就可以作为方法返回地址
方法异常退出时,返回地址需要通过异常处理表确定,栈帧中一般不会保存。
当方法返回时,可能进行3个操作:
- 恢复上层方法的局部变量表和操作数栈
- 把返回值(如果有的话)压入调用者栈帧的操作数栈
- 调整 PC 计数器的值以指向方法调用指令后面的一条指令
3 本地方法栈
本地方法栈和java虚拟机栈所发挥的作用非常相似、区别是java虚拟机栈是为虚拟机执行java代码服务,而本地方法栈则是为虚拟机使用的本地方法服务。Native方法是最初java为了兼容c、c++等其他语言而提供的调用其他语言代码的方式。
与虚拟机栈一样的是,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverFlowErrorhe和OutOfMemoryError异常。
4 java堆
对java应用程序来说java堆是虚拟机管理的内存中最大的一块。java堆是垃圾回收器管理的内存区域。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
主流虚拟机的堆内存都是可扩展的(通过参数-Xmx和-Xms设置)。如果在java堆中没有内存完成实例分配,并且堆再也无法扩展时,虚拟机会抛出OutOfMemoryError异常。
5 方法区
方法区和java堆一样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
垃圾回收在这个区域很少出现,对这个区域的垃圾回收目标主要是针对常量池的回收和类型的卸载。
如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
6 运行时常量池
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存储编译器生成的各种字面量和符号引用,这部分信息将在类加载后存放到方法区的运行时常量池中。除符号引用外,由符号引用翻译出来的直接引用也存储在运行时常量池中。
运行时常量池相对于Class文件常量池的另一个重要特征是具备动态性,运行期间也可以将新的常量放入池中,这种特性用到比较多的是String类的intern()方法。
7 直接内存
直接内存并不是虚拟机运行时数据区的一部分,但这部分内存也被频繁使用,且有可能导致OutOfMemoryError异常。
NIO,可以使用Native函数库直接在堆外分配内存,通过在java堆里的DirectByteBuffer对象作为这块内存的引用进行操作。这样避免了数据在java堆中和Native堆中复制数据,可以显著提升性能。
这块内存不会受到Java堆大小的限制,但会受到本机内存大小的限制,在本机内存不多,不满足直接内存扩展需要时,会出现OutOfMemoryError异常。