JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,有着自己完善的硬件架构,如处理器,堆栈等,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可以在多个平台上不加修改的运行。
Java文件必须先通过一个叫javac
的编译器,将代码编成class文件,然后通过JVM把class文件解释成各个平台可以识别的机器码,最终实现跨平台运行代码。
JVM 内存模型可以分为两部分,堆和方法区是所有线程所共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的。
在JVM1.8中,图中的 方法区为元数据区 。下面谈一谈五个区域的作用。
JVM 管理的最大的一块内存区域,存放着对象的实例,是线程共享区。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。可以通过参数 -Xmx -Xms 来指定运行时堆内存的大小,如果在Java堆中没有内存完成实例分配,并且堆也无法再 扩展时,Java虚拟机将会抛出OutOfMemoryError异常。
堆是垃圾收集器管理的主要区域,因此也被称为 “GC(Garbage Collected Heap)堆” 。
从内存回收的角度上看,可分为新生代(Eden空间,From Survivor空间、To Survivor空间)及老年代(Tenured Gen)。
从内存分配的角度上看,为了解决分配内存时的线程安全性问题,线程共享的JAVA堆中可能划分出多个线程私有的分配缓冲区(TLAB)。
从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区 (Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如 何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。
虚拟机为新生对象分配内存时,存在可能出现正在给对象A分配内存,指针还未修改,对象B又同时使用原来的指针分配内存的情况(指针用于划分内存使用空间和空闲空间)。TLAB 的存在就是为了解决这个问题:每个线程在Java堆中预先分配一小块内存 TLAB,线程需要分配内存时首先在自己的TLAB上进行分配,若TLAB用完并分配新的TLAB时,再加同步锁定,这样就大大提升了对象内存分配的效率。
其实方法区是在JDK1.8以前的版本里存在的一块内存区域,主要就是存放从class文件里加载进来的类的,而且常量池也是在这块区域内的。
但是在JDK1.8之后,这块区域摇身一变,换了名字,叫做“Metaspace”,翻译过来就是“元数据空间”的意思,当然它只是改了个名,实现的功能是没变的。
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器后的代码缓存等数据。
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
①这个类型的完整有效名称(全名=包名.类名)
②这个类型直接父类的完整有效名(对于interface或是java.lang.0bject,都没有父类)
③这个类型的修饰符(public, abstract,final的某个子集)
④这个类型直接接口的一个有序列表
JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public, private,protected,static,final, volatile, transient的某个子集)
当有多个线程都用到一个类的时候,而这个类还未被加载,则应该只有一个线程去加载类,让其他线程等待。
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈,每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用。
虚拟机栈的作用:主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。
每个方法被执行的时候都会创建一个”栈帧”,用于存储局部变量表(包括参数)、操作栈、方法出口等信息。
每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈的基本元素,栈帧由局部变量区、操作数栈等组成,如下图所示:
每一个栈帧包含的内容有局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译代码时,栈帧需要多大的局部变量表,多深的操作数栈都可以完全确定的,并写入到方法表的code属性中。
这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚 拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩 展时无法申请到足够的内存会抛出OutOfMemoryError异常。
与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机 栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失 败时分别抛出StackOverflowError和OutOfMemoryError异常。
普通开发可以忽略二者的区别,JDK1.8 HotSpot虚拟机直接就把本地方法栈和虚拟机栈合二为一。
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的 字节码的信号指示器。字节码解释器工作时就是通过改变这个计数器 的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处 理、线程恢复等基础功能都需要依赖这个计数器来完成。 由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一 个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因 此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程 之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
此内存区域是唯 一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
Java Virtual Machine (JVM) 内存模型是指在 Java 程序执行过程中,JVM 是如何管理内存的一种抽象描述。它定义了 JVM 在运行 Java 程序时,如何划分和使用内存区域,以及各个区域的作用和生命周期。以下是 JVM 内存模型的主要组成部分的小结:
JVM 内存模型的具体实现可能因不同的 JVM 版本和厂商而有所不同。随着 JVM 的不断发展,一些细节可能会有所改变,例如使用 Metaspace 替代永久代等。因此,在实际应用中,要注意 JVM 的具体配置和版本,以充分利用内存,并避免出现内存相关的性能问题。