JDK8的JVM内存模型小结
网络上有很多关于JVM内存模型的文章,本文只针对JDK8的JVM内存模型予以小结,若有不对之处望留言指正。
其中,绿色表示线程私有空间,其他表示线程可共享空间
程序计数器 (无GC)
线程私有的一块较小的内存空间,当前线程所执行的字节码的行号指示器,JVM的多线程就是通过线程轮流切换并分配CPU时间分片的方式来实现的,在任意指定的时刻,一个处理器(一个内核)只会执行一个线程中的指令,为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各个线程之间互不影响,独立存储。
这个区域也是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域。
java虚拟机栈 (无GC)
生命周期与线程相同,是java方法执行的内存模型,每个方法在执行的同时会在虚拟机栈中创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、返回值地址等信息。每个方法从调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。出栈相当于清空了数据,入栈出栈的时机很明确,所以这块区域内存也是不需要GC。
虚拟机栈有2种异常:
1、StackOverflowError: 线程请求的栈深度大于虚拟机所允许的深度,特别是在有递归调用时
2、OutOfMemoryError: 虚拟机栈无法满足线程所申请的空间需求,即使经过动态扩展任然无法满足,将会导致该类异常的发送
本地方法栈 (无GC)
与虚拟机栈类型,主要区别在于虚拟机栈为虚拟机执行java方法服务,而本地方法栈是为运行本地native方法而服务,这块区域也是不需要进行GC的,所谓本地方法就是一个java调用非java代码的接口,该方法非java语言实现,大多数由C语言实现,Java通过JNI的方式来调用本地方法,而本地方法是以库文件的方式存放的(Unix机器上是以SO文件形式)。通过调用本地库文件的内部方法,使java可以实现和本机器的紧密联系,调用系统级的各接口方法,当调用java方法时虚拟机会创建一个栈帧并压入java栈,而当调用本地方法时,JVM会保持java栈不变,不会在java栈中压入新的栈,只是简单的动态链接并直接调用本地方法。
本地方法栈也有2种异常:
1、StackOverflowError: 线程请求的栈深度大于本地方法栈所允许的深度时
2、OutOfMemoryError: 本地方法栈空间需求不足,即使经过动态扩展任然无法满足时
堆 (GC发生的区域)
堆是JVM内存占用最大,管理最复杂的一个区域,堆中主要存放对象的实例和数组,也是GC主要发生的区域。
堆空间大小不满足时将抛出OutOfMemoryError异常。
本地内存 (无GC)
本地内存,也就是通常所说的堆外内存,包含元数据区和直接内存,也称为C-Heap,是供JVM自身进程使用的,当java堆空间不足时会触发GC,但本身不会触发GC
元数据区
JVM规范中的方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据。在JDK8中,元数据区是对方法区的一个实现,但不在虚拟机中,而是使用本地内存,默认情况下,可用于元数据区的本地变量是没有限制的,但可以用MaxMetaspaceSize参数来设置上限值
元数据区会抛出OutOfMemoryError异常
直接内存
在JDK1.4中引入的NIO模型,一种基于channel与buffer的IO方式,可以使用Native库函数直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,能在一些场景中显著提高性能,因为避免了而在java堆中和Native堆中来回复制数据,在操作系统IO过程中,需要把数据从用户态copy到内核态,然后再输出到IO设备,所以从java堆内存输出到IO设备需要经过2次IO,而DirectMemory在native堆上只需要一次copy
直接内存同样会抛出OutOfMemoryError异常