深入理解java虚拟机
本博客所有内容为阅读《深入理解java虚拟机》小结,如有侵权,请联系删除。
运行时数据区域
线程共享的数据区
堆
对于java应用程序来说,堆是虚拟机所管理的内存中最大的一块。虚拟机启动时创建,此内存的唯一目的就是存放对象实例。从内存分配的角度看,堆可以划分出多个线程私有的分配缓冲区(ThreadLocalAllocationBuffer,TLAB),以提升对象分配时的效率。
根据java虚拟机规范,堆可以处于物理上不连续的内存空间中,但在逻辑上应该被视为连续的。
java堆既可以是固定大小的,也可以是可扩展的,通过参数-Xmx和-Xms设定。如果在java堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出OOM。
方法区
方法区存储已被虚拟机加载的类型信息、常量、静态变量、即时编译期编译后的代码缓存等数据。
方法区有一部分被划分为运行时常量池,用于存放编译期生成的各种字面量与符号引用,运行时常量池相对于class文件常量池有更好的动态性。
线程隔离的数据区
虚拟机栈
生命周期与线程相同,虚拟机栈描述的是java方法执行的线程内存模型,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
局部变量表存放了编译期可知的各种java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型(指向了一条字节码指令的地址)。
这些数据类型在局部变量表中的存储空间以局部变量槽(slot)来表示,其中64位长度的long和double类型的数据会占用两个槽,其余的数据类型只占用一个槽。局部变量表所需的内存空间在编译期完成分配,方法运行期间不会改变局部变量表的大小(槽的数量)。
java虚拟机规范中,对这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出SOE(stackoverflowerror),如果虚拟机栈容量可以动态扩展,当栈扩展无法申请到足够的内存会抛出OOM(hotspot虚拟机不可以动态扩展)。
本地方法栈
与虚拟机栈类似,为虚拟机使用到native方法服务的。
程序计数器
程序计数器是一块较小的内存,它可以看作当前线程所执行的字节码的行号指示器。在java虚拟机的概念里面,字节码解释器工作时就是通过改变这个计数器的值来选取需要执行的字节码指令。因为java的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,因此每个线程都需要一个程序下计数器来保证线程切换之后能恢复到原来的位置,且这个计数器需要线程隔离。如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果线程正在执行的是一个native方法,则这个计数器的值应该为空(undefined),此内存区域是java虚拟机规范中唯一未指定任何OOM情况的区域。
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域,但是这部分也被频繁的使用,而且也可能导致oom,所以放在这里一起学习。
在jdk1.4中新加入了NIO(new Input/output)类,引入了一种基于通道(channel)与缓冲区(Buffer)的I/O方式,它可以使用native函数库直接操作堆外内存,然后通过一个存储在java堆里面的directByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和native堆中来回复制数据,设置-Xmx等参数信息时,如果忽略直接内存,使得各种内存区域总和大于物理内存限制,则会导致OOM。