JVM内存结构相关知识解析
最近在看《JAVA并发编程实践》这本书,里面涉及到了Java内存模型,通过Java内存模型顺理成章的来到的JVM内存结构,关于JVM内存结构的认知还停留在上大学那会的课堂上,一直没有系统的学习这一块的知识,所以这一次我把《深入理解Java虚拟机JVM高级特性与最佳实践》、《Java虚拟机规范JavaSE8版》这两本书中关于JVM内存结构的部分都看了一遍,算是对JVM内存结构有了新的认识。JVM内存结构是指:Java虚拟机定义了若干种程序运行期间会使用的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁,另一些则与线程一一对应,随着线程的开始而创建,随着线程的结束而销毁。具体的运行时数据区如下图所示:
在Java虚拟机规范中,定义了五种运行时数据区,分别是Java堆、方法区、虚拟机栈、本地方法区、程序计数器,其中Java堆和方法区是线程共享的。接下来就具体看看这五种运行时数据区。
Java堆(Heap)
Java堆是所有线程共享的一块内存区域,它在虚拟机启动时就会被创建,并且单个JVM进程有且仅有一个Java堆。Java堆是用来存放对象实例及数组,也就是说我们代码中通过new关键字new出来的对象都存放在这里。所以这里也就成为了垃圾回收器的主要活动营地了,于是它就有了一个别名叫做GC堆,根据垃圾回收器的规则,我们可以对Java堆进行进一步的划分,具体Java堆内存结构如下图所示:
我们可以将Java堆划分为新生代和老年代两个大模块,在新生代中,我们又可以进一步分为Eden空间、FromSurvivor空间(s0)、ToSurvivor空间(s1),Survivor空间有一个为空,用于发生GC时存放存活对象,老年代存放的是经过多次MinorGC仍然存活的对象或者是一些大对象,FGC就是发生在老年代。
上面就是Java堆的具体结构,我们也知道Java堆中的各空间大小,我们是可以动态控制的,这个在图中我也进行了简单的标注,下面我们一起来详细的了解一下这三个参数:
- -Xms:JVM启动时申请的初始Heap值,默认为操作系统物理内存的1/64,例如-Xms20m
- -Xmx:JVM可申请的最大Heap值,默认值为物理内存的1/4,例如-Xmx20m,我们最好将-Xms和-Xmx设为相同值,避免每次垃圾回收完成后JVM重新分配内存;
- -Xmn:设置新生代的内存大小,-Xmn是将NewSize与MaxNewSize设为一致,我们也可以分别设置这两个参数
在Java堆中会发生OOM异常,当我们的Java堆内有足够的空间去完成实例分配时,并且堆也无法扩展,将会抛出我们常见的OutOfMemoryError异常,如下图所示:
关于OOM异常,我还是想多说一句,网上有一道非常火的面试题:JVM堆内存溢出后,其他线程是否可继续工作?,我个人觉得不少回答是错误的,有兴趣的可以研究一下。
方法区(MethodArea)
方法区(MethodArea)与Java堆一样,是各个线程共享的内存区域,是Java虚拟机中唯二的内存共享区域。在Java虚拟机规范中是这样定义方法区的:它存储了每个类的结构信息,例如运行时常量池、字段、方法数据、构造函数和普通方法的字节码内容,还包括一些在类、实例、接口初始化时用到的特殊方法。
方法区在虚拟机启动的时候被创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集与压缩,方法区在实际内存空间中可以不是连续的,对于方法区的容量,你可以是固定的,也可以随着程序的执行动态扩展,并且在不需要过多空间时自动收缩。
上面都是Java虚拟机中的规范,来看看具体的实现,拿我们常用的HotSpot虚拟机来说,在JDK1.8之前,方法区也被称作为永久代,这个方法区会发生我们常见的java.lang.OutOfMemoryError:PermGenspace异常,我们也可以通过启动参数来控制方法区的大小:
- -XX:PermSize设置最小空间
- -XX:MaxPermSize设置最大空间
在JDK1.8之后,HotSpot虚拟机对方法区进行了不小的改动,彻底移除了永久代,将原来存放在永久代的数据迁移至Java堆或者Metaspace,方法区被移至到了Metaspace,字符串常量移至JavaHeap,换句话说就是JDK1.8开始,Metaspace也就是我们所谓的方法区,为什么要做这个改变呢?也许是基于以下两点原因:
- 由于PermGen内存经常会溢出,引发恼人的java.lang.OutOfMemoryError:PermGen,因此JVM的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的OOM
- 移除PermGen可以促进HotSpotJVM与JRockitVM的融合,因为JRockit没有永久代。
我们也可以通过设置参数来控制Metaspace的空间大小,主要有以下几个命令:
- -XX:MetaspaceSize:分配给类元数据空间(以字节计)的初始大小。MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。
- -XX:MaxMetaspaceSize:分配给类元数据空间的最大值,超过此值就会触发FullGC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。
- -XX:MinMetaspaceFreeRatio:表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最小比例,不够就会导致垃圾回收。
- -XX:MaxMetaspaceFreeRatio:表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最大比例,不够就会导致垃圾回收。
Java虚拟机栈(JVMStacks)
每一条Java虚拟机线程都有自己私有的Java虚拟机栈,这个Java虚拟机栈跟线程同时创建,所以它跟线程有相同的生命周期。Java虚拟机栈描述的是Java方法执行的内存模型:每一个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在Java虚拟机栈中的入栈到出栈的过程。
局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
Java虚拟机栈既允许被实现成固定的大小,也允许根据计算动态来扩展和收缩,如果采用固定大小的话,每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。在Java虚拟机栈中会发生两种异常,这个在虚拟机规范中有指出:
- 如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出StackOverflowError异常;
- 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存或者在创建新的线程时没有足够的内存去创建对应的Java虚拟机栈,那么虚拟机将会抛出OutOfMemoryError异常。
程序计数器(ProgramCounterRegister)
程序计数器也是线程私有的,它只需要一块较小的内存空间,你可以把它看作当前线程所执行的字节码的行号指示器,在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
我们知道在多线程的情况下,并不是一条线程一直执行完,而是多个线程轮流切换执行,所以为了线程切换后能够恢复到正确的执行位置,我们就需要程序计数器来告诉线程接下来该执行哪条指令。如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址,如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。
需要特别注意的是,程序计数器是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
本地方法栈(NativeMethodStacks)
本地方法栈(NativeMethodStacks)与Java虚拟机栈所发挥的作用是非常相似的,其区别不过是Java虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如SunHotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
与Java虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
参考
- 《深入理解Java虚拟机JVM高级特性与最佳实践》
- 《Java虚拟机规范JavaSE8版》
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。