JVM运行时数据区
JVM运行时数据区
JVM运行时数据区概述
灰色区域为单独线程私有的,红色的为多个线程共享的。
即:如下图
- 每个线程:独立包括程序计数器、虚拟机栈、本地方法栈
- 线程间共享:堆、堆外内存(永久代或元空间,代码缓存)
每个JVM只有一个Runtime实例,相当于内存结构图中的运行时数据区。
线程
- 线程是一个程序里的运行单元,JVM允许一个应用有多个线程并行执行。
- 在HotSpot JVM中,每个线程都与操作系统的本地线程直接映射。(当一个Java线程准备好执行以后,此时一个操作系统的本地线程也同时创建。Java线程执行中止后,本地线程会回收)
- 操作系统负责所有线程的安排调度到任何一个可用CPU。一旦本地线程初始化成功,它就会调用Java线程中的run()方法。
程序计数器(PC Register)
JVM的PC寄存器是对物理PC寄存器的一种抽象模拟。
- 它是一块很小的内存空间。也是运行速度最快的存储区域。
- 每个线程都有独立的程序计数器,是线程私有的,生命周期与线程生命周期一致。
- 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程在执行Java方法的JVM指令地址,如果是native方法,计数器值为空(Undifined)。
- 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能需要计数器来完成
- 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
- 此内存区域是JVM规范唯一一个没有任何OOM的区域。
作用:PC寄存器是用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
两个常见问题
- 使用PC寄存器存储字节码地址有什么用?为什么使用PC寄存器记录当前线程的执行地址?
因为CPU需要不停切换各个线程,这时候切换回来以后,就知道接着从哪继续执行。JVM字节码解释器就需要改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。
为了更够准确记录各个线程正在执行的当前字节码质量地址,最好的办法就是每个线程分配一个PC寄存器。
虚拟机栈
- Java虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。同样,线程私有,生命周期与线程一致。
- 每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
详细参考:https://www.cnblogs.com/old-cha/p/13089555.html
本地方法接口
本地方法
简单来说,一个Native Method就是一个Java调用非Java代码的接口。
在定义一个native method时,并不提供实现体,因为其实现体是由非Java语言在外面实现。
标识符native可以与其他java标识符连用,但abstract除外。
- 为什么要使用Native Method
Java使用方便,但有些层次的任务Java实现不容易,或者在意效率时。
1. 与Java环境外交互:**有时Java应用需要与Java外面的环境交互,这是本地方法存在的主要原因。**本地方法提供非常简洁的接口,而且无需了解Java应用之外的繁琐细节。
2. 与操作系统交互:JVM支持着Java语言本身和运行时库,经常依赖一些底层系统的支持。**通过使用本地方法,得以用Java实现jre的与底层系统的交互,甚至JVM一部分是使用C编写。**
- 现状:
目前该方法使用越来越少,除非是与硬件有关的应用。
- 本地接口:
作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序。
本地方法栈
- Java虚拟机栈管理Java方法调用,而本地方法栈用于管理本地方法的调用。
- 本地方法栈,也是线程私有的。
- 允许被实现成固定或者可动态扩展的内存大小。(在内存溢出方面是相同的)
- 如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机会抛出一个StackOverflowError异常。
- 如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够内存,或者在创建新的线程时没有足够内存创建,会抛出OOM异常。
- 本地方法使用C语言实现。
- 它的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载本地方法库。
- 当某个线程调用一个本地方法时,它就进入了一个全新的并且不受虚拟机限制的世界,它和虚拟机拥有同样的权限。
- 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区。
- 它甚至可以直接使用本地处理器中的寄存器。
- 直接从本地内存的堆中分配任意数量的内存。
- 并不是所有JVM都支持本地方法。因为Java虚拟机规范没有明确要求本地方法栈使用的语言、具体实现方式、数据结构等。
- 在HotSpot中,直接将本地方法栈和虚拟机栈合二为一。
堆
- 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域。(如下例1)
- Java堆在JVM启动时被创建,其空间大小也就被确定了。是JVM管理的最大一块内存空间。(JVM堆内存的大小是可调节的)
- 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被是视为连续的。
- 所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。
- Java虚拟机规范中描述:所有对象实例以及数组都要在堆上分配。(从实际角度来看,“几乎”是所有的对象实例都在这里分配)
- 数组和对象可能永远不会存储在栈上,因为栈帧中保存引用,这个引用指向对象或者数组在堆中的位置。
- 在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除。
- 堆,是GC执行垃圾回收的重点区域。
细节参考:https://www.cnblogs.com/old-cha/p/13216004.html
方法区
《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者压缩”。但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
所以,方法区看做是一块独立于Java堆的内存空间。
- 方法区(Method Area)和Java堆一样,是各个线程共享的内存区域
- 方法区在JVM启动的时候被创建,并且它的实际物理内存空间中和Java堆区一样都是可以不连续的。
- 方法区的大小,跟堆空间一样,可以选择固定大小或者拓展。
- 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多类,导致方法区溢出,虚拟机同样会溢出错误:java.lang.OutOfMemoryError: PerFen space或者java.lang.OutOfMemoryError:Metaspace
- 加载大量第三方jar包;Tomcat部署的工程过多(30-50个);大量动态生成的反射类
- 关闭JVM就会释放这个区域的内存。
细节参考:https://www.cnblogs.com/old-cha/p/13264114.html
对象的实例化
细节参考:https://www.cnblogs.com/old-cha/p/13276634.html
直接内存
- 不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。
- 直接内存是在Java堆外的、直接向系统申请的内存区间。
- 来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存。
- 通常,访问直接内存的速度会优于Java堆。即读写性能高。
- 因此处于性能考虑,读写频繁的场合可能会考虑直接内存。
- Java的NIO库允许Java程序直使用直接内存,用于数据缓冲区。
- 也可能导致OOM异常。(OOMError:Direct buffer memory)
- 由于直接内存在Java堆外,因此它的大小不会直接受限于 -Xmx 指定的最大堆大小,但是系统内存是优先的,Java堆和直接内存总和依然受限于操作系统能给出的最大内存。
- 缺点
- 分配回收成本高
- 不受JVM内存回收管理
- 直接内存大小可以通过 -XX:MaxDirectMemorySize=size 设置
- 如果不指定,默认与堆的最大值 -Xmx 参数一致。

浙公网安备 33010602011771号