JVM体系结构
基本概念
JVM 是可运行 Java 代码的假想计算,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收、堆和一个存储方法域。 JVM 是运行在操作系统之上的,它与硬件没有直接的交互。
JVM内存模型
完整内存模型图见:https://www.processon.com/diagraming/5d42ca2ce4b043dcf84e8764
- JVM 内存区域主要分为:线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【 JAVA 堆、方法区】、直接内存。
- 线程私有区域生命周期与线程相同,依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot VM 内,每个线程都与操作系统的本本地线程直接映射,因此这部分内存区域的存/否跟随本地线程的生/死对应)。
- 线程区域随虚拟机的启动/关闭而创建/销毁。
- 直接内存并不是 JVM 运行时数据区的一部分,但也会被频繁的使用:在 JDK1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 方式,它可以使用 Native 函数库直接分配堆外内存,然后使用 DirectByteBuffer 对象作为这块内存的引用进行操作,这样就避免了在 Java 堆和 Native 堆中来回复制数据,因此在一些场景中可以显著提高性能。
程序计数器(线程私有)
- 字节码解释器从程序计数器中取出指令,实现对流程的控制
在线程切换的过程中,保存当前线程的执行位置,以便在线程恢复执行的时候找到正确的执行位置
唯一一个不会发生 OutOfMemoryError 的区域。
Java虚拟机栈(线程私有)
- 由一组栈帧组成,栈帧用来保存局部变量、操作数栈、动态链接、方法出口等信息,栈帧随着方法调用而创建,随着方法结束而销毁—-无论方法是正常完成还是异常完成(抛出了在方法内未捕获的异常)都算方法结束。
- 局部变量表主要存放了编译期可知的各种数据类型(boolean,byte,char,short,int,float,long,double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的应用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
- StackOverFlowError:若 java 虚拟机的内存大小不动态扩展,那么当线程请求栈的深度超过当前 java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
- OutOfMemory:若 java 虚拟机堆中没有空闲内存,并且垃圾收集器也无法提供更多的内存。就会抛出 OutOfMemoryError 错误。
- 每一次函数的调用就是对应栈帧的一次入栈到出栈的过程。
本地方法栈(线程私有)
- 使用到的是本地方法服务。
- HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
- 线程私有区域,他的生命周期随着线程的创建而开始,随着线程的消亡而结束。
堆( Heap-线程共享 )-运行时数据区
目的是存放对象实例,几乎所有的对象实例和数组都在这里分配内存
随着JTI编译器的发展和逃逸分析技术的成熟,栈上分配、变量替换优化技术,使得所有对象在堆上的分配变得不那么绝对。如果某些方法中的对象引用没有被返回或者未被外面使用,那么对象可以直接在栈上分配内存。
堆是垃圾收集器管理的主要区域,因此才被称为GC堆,现在垃圾收集器都是采用分代收集算法。Java堆细分:新生代和老年代,在细致一点的话就是:伊甸区,from区,to区。划分的目的是更好的回收内存,或者更快的分配内存。
方法区/永久代(线程共享)
- 用于存放已被虚拟机加载的类信息,常量,静态变量,即时编译器后的代码数据。
- java虚拟机规范把方法区描述为堆的一个逻辑部分,Non-Heap(非堆)
- 永久代就是HotsSpot虚拟机堆虚拟机规范方法区中的一种实现方式。也就是说,永久代是HotSpot的概念,方法区是java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现。
- -XX:PermSize=N //方法区(永久代)初始大小
- -XX:MaxPermSizze=N //方法区(永久代)最大大小,超过这个值就会抛出OutOfMemoryError异常:java.lang.OutOfMemoryError:PermGen
为什么元区间取代了永久代
- 永久代在 JVM 中受到 JVM 本身固定大小的限制,无法进行调整。
- 元空间使用的是直接内存,受本机可用内存的限制
- 元空间内存溢出:java.lang.OutOfMemoryError:MetaSpace
- -XX:MaxMetaspaceSize 标志设置最大元空间大小,默认值为 unlimited ,这意味着他只受系统内存的限制
- 元空间里面存在的是元数据,这样加载多个类的元数据就不由 MaxPermSize 控制,而是由系统的实际可用空间来控制,这样能加载更多的类
- 在 JDK8 中,合并 HotSpot 和 Jrockit 的代码时,Jrockit 从来没有一个叫永久代的东西,合并之后就没有必要额外的设置这么一个永久代的地方了
运行常量池
- java7之前,运行时常量池包含字符串常量池。
- java7,运行时常量池在永久代,字符串常量池在堆中。
- java8,运行时常量池在元空间,字符串常量池在堆中。
直接内存
- 本机的内存
- JDK1.4 之后的 NIO ,引入了一种基于通道与缓存区的 I/O 方式,可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的应用操作,这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据