JVM之内存模型

105 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

JVM的内存模式

JVM 运行时内存共分为虚拟机栈元空间程序计数器本地方法栈五个部分。还有一部分内存叫直接内存,属于操作系统的本地内存,也是可以直接操作的。 image-20220709152016354 线程共享的: 方法区

线程私有的: 程序计数器虚拟机栈本地方法栈

  • 元空间(方法区)

    本质和永久代类似,都是对JVM规范中方法区的实现。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。在jdk1.8以前,称为永久代,在1.8以后,称为元空间MateSpace,不由具名管理它的内存结构,而是交给操作系统内存,元空间使用的系统内存,元空间的串池StringTable被移到了堆内存中。线程共享,方法区逻辑上是堆的一部分,方法存储了更类结构相关的一些信息,比如常量,类变量,类的构造器,方法的信息,成员方法和构造方法,编译器编译后的代码等等,方法区如果内存不足也会报内存溢出。

  • 虚拟机栈

    每个线程都有一个私有的栈,随着线程的创建而创建生命周期与线程相同。栈里面存着的是一种叫“栈帧”的东西,每个方法都会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用)、操作数栈、方法出口等信息,栈帧与数据结构的栈类似,都是先进后出的数据结构,只支持出栈和入栈操作。栈的大小可以固定也可以动态扩展

    存在栈内存溢出的问题;栈的大小是固定的,可能由于方法的递归调用没有设置正确的方法结束的调教导致,栈一直入栈没有出栈,使得最后栈帧的内存大小超过栈的内存;栈帧过大导致内存溢出;

  • 本地方法栈

    与虚拟机栈类似,区别是虚拟机栈执行Java方法,本地方法执行native方法。在虚拟机规范中对本地方法栈中方法使用的语言、使用方法与数据结构没有强制规定,因此虚拟机可以自由实现它。

  • 程序计数器

    程序计数器是一块较小的内存空间,可以看成是当前线程所执行的字节码行号指示器。在任何一个确定的时刻,一个处理器(对于多内核来说是一个内核)都只会执行一条线程中的指令。

    字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。

    为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,我们称这类内存区域为“线程私有”内存。在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次的运行位置。

    程序计数器是唯一不会出现OutOfMemoryError的内存区域,生命周期随着线程的创建而创建,随着线程的结束而死亡。仅仅只是一个运行指示器,存储下一个待执行的命令的地址。无论代码多少,在最坏情况下死循环也不会让这块内存超限。

  • 堆内存

    堆内存是 JVM 所有线程共享的部分,在虚拟机启动的时候就已经创建。所有的对象和数组都在堆上进行分配。这部分空间可通过GC(垃圾回收器)进行回收。当申请不到空间时会抛出 OutOfMemoryError。堆是JVM内存占用最大,管理最复杂的一个区域。其唯一的用途就是存放对象实例:所有的对象实例及数组都在对上进行分配。jdk1.8后,字符串常量池从永久代中剥离出来,存放在队中。

  • 直接内存

    直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中农定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用native 函数库直接分配堆外内存,然后通脱一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。