运行时数据区域
上图中黄色部分是线程私有的
程序计数器
- 程序控制流的指示器,基础的分支、循环、跳转、异常处理、线程恢复等功能都依赖它
- 在java虚拟机的概念模型中,字节码解释器工作时就是通过改变它(计数器)的值,来选取下一条需要执行的字节码指令
- 它在java虚拟机中是线程私有的。原因:一个处理器的各条线程进行工作时,为了保证它们切换后都能恢复到正确的执行位置,每个线程都需要有独立的程序计数器。各个程序计数器间不能互相干扰
- 如果线程正在执行一个java方法,则这个计数器记录的是正在执行的字节码指令的地址;如果执行的是本地(native)方法,这个计数器值则为空(Undefined)。
- 它不会出现OutOfMemoryError错误。原因:它仅仅是储存一条指令的地址,这个地址总不会超出这块内存的大小,所以不会出现内存溢出
java虚拟机栈
-
为虚拟机执行java方法(也就是字节码)服务
-
同样它也是线程私有的
-
它的生命周期和线程同样
-
它描述的是java方法执行的线程内存模型:每个方法执行的时候,java虚拟机都会同步创建一个栈帧用来存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
通常我们讲的“栈”就是指这里的虚拟机栈,或者更多的情况下仅指虚拟机栈中局部变量表的部分
局部变量表
- 局部变量表存放了编译期可知的各种java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型(指向一条字节码指令地址)
对象引用,并不是对象本身,它可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象有关的位置
- 上述的数据类型在局部变量表中的存储空间以局部变量槽(Slot)表示。64位长度的long和double类型占用两个Slot,其余占用一个。
- 局部变量表所需的内存空间在编译期完成分配,当进入一个方法时,这个方法需要在栈帧分配的局部变量空间是完全确定的,方法运行期间不会改变局部变量表的大小。这里的大小是指变量槽的数量。
StrackOverflowError:线程请求的栈深度大于虚拟机所允许的深度时,抛出该异常
OutOfMemoryError:虚拟机栈容量是可以动态扩展的(有的虚拟机实现是不行的例如HotSpot),当栈扩展时无法申请到足够的内存时,抛出该异常
本地方法栈
其与虚拟机栈所发挥的作用非常相似,本地方法栈为虚拟机提供使用本地方法(native)服务
java堆
- java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建
- 该内存块唯一的目的就是存放对象实例,java中“几乎”所有的对象实例都在这里分配内存
java虚拟机规范中对java堆的描述:所有的对象实例以及数组都应当在堆上分配
这里用到几乎,而未用全部,则是考虑到现在java的发展,现在已经初见端倪了,日后可能出现值类型的支持,且目前来说,由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致了一些微妙的变化,所以java对象实例都分配在堆上也渐渐变得不那么绝对了
- 另一点,有的资料上提到的“新生代、老年代、永久代”等等一些内容,在十年前(以G1收集器出现为分界),是不太会有歧义,但到了今天垃圾回收技术已经不可同日而语了,HotSpot里面也出现了不采用分代设计的新垃圾收集器,在按照上面的说话就不太合适了
- java堆可以处于物理上不连续的内存空间中,但在逻辑上应该视它为连续的,所以java堆即可以被实现成固定大小的,也可以进行扩展。也就是我们可以通过
-Xmx 和-Xms进行设定
- 如果java堆没有完成实例分配,且堆也无法再进行扩展时,虚拟机会抛出OutOfMemeryError异常
方法区
- 方法区与堆一样,是各个线程共享的内存区域,它用来存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译后的代码缓存等数据
- 方法区在java8以前,HotSPot虚拟机设计团队将其设置成“永久代”,现在看来是不合理的,这就导致,其极易出现内存泄漏,永久代有 -xx:MaxPerSize的上限,即使不设置也有默认大小
- 在java虚拟机规范中,对方法区的约束是非常宽松的,除了和堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾回收。
- 方法区中,垃圾回收在方法区确实不太需要,但是不是数据进入了方法区就如永生代一样“永久存在了”。
- 方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常
运行时常量池
- 运行时常量池是方法区的一部分。
- Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池表,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
- 虚拟机对Class文件的每一部分(包括常量池)的格式都有严格规定,只有符合规范,才能被虚拟机认可、加载和执行。但对于运行时常量池,《java虚拟机规范》并没有做任何细节的要求。
直接内存
- 直接内存并不是虚拟机运行时数据区的一部分,也不是《java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemeryError的出现。
- jdk4中新加入了NIO(New Input/Output)类,引入了一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样可以避免在java堆和native堆中来回复制数据。
- 另外,虽然直接内存不会收到jvm堆大小的限制,但是,它既然是内存,就会受到当前机器的总体内存大小以及处理器寻址空间的限制。所以我们在配置java堆内存时需要考虑直接内存,否则会导致动态扩展时出现OutOfMemoryError异常
HotSpot虚拟机对象
简介
- 目前,JVM中最广泛应用的虚拟机是HotSpot
- 为了解决虚拟机中内存数据如何创建,如何布局,如何访问等的问题,这里就以HotSpot虚拟机和java堆举例
对象的创建
创建对象对于我们(java程序猿)来说,new这个关键字是最常用的,这里我们不讨论数组和Class对象.
- 当java虚拟机遇到一条字节码new指令时,先检查这个指令的参数是否能再常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载、解析和被初始化过。如果没有,则执行类加载过程。