02|JVM内存模型

1. JVM整体结构及内存模型

在这里插入图片描述

1.1 类装载子系统

负责加载字节码文件并将其转换为可以执行的Java类。类加载器子系统包括三个主要的类加载器:Bootstrap ClassLoader(引导类加载器)、Extension ClassLoader(扩展类加载器)和 Application ClassLoader(应用程序类加载器)。它们负责从不同的位置加载类文件, 加载到Runtime data area 中的method area(方法区)

1.2 字节码执行引擎

负责执行Java字节码指令。执行引擎包括解释器和即时编译器。解释器逐条解释字节码指令并执行相应的操作,而即时编译器将字节码编译成本地代码以提高执行效率。

1.3 本地方法接口(Native Interface)

与native libraries(本地方法库)交互,是其他编程语言交互的接口。

1.4 运行时数据区(JVM内存模型)

包括方法区、堆、虚拟机栈、本地方法栈程序计数器。这些区域负责存储运行时数据,包括类的信息、对象实例、方法的局部变量和操作数栈等。

1.4.1 程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令(每执行完一个指令,字节码执行引擎会立刻动态的修改程序技术器中的值(因为是字节码执行引擎执行的,所以字节码执行引擎每执行完一行代码,会去修改程序计数器中的值)),它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。此内存区域是唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域,生命周期随着线程的创建而创建,随着线程的结束而死亡

1.4.2 JAVA虚拟机栈

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。栈会给每个线程分配一个私有的栈,其内部保存一个个的栈帧。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接(在程序运行时将符号引用替换为直接引用(常量池中的内存地址),常量池是放在方法区中)、方法出口(存储的是主方法调用子方法的地址)等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。
特点是FILO(先进后出),拿数据从栈顶拿;局部方法先执行完,执行完后,局部方法的局部变量内存空间全部释放掉(出栈)

1.4.3 堆

Java堆(Java Heap)是虚拟机所管理的内存中最大的一块,所有线程共享,在虚拟机启动时创建;
唯一目的就是存放对象实例,几乎所有的对象实例以及数据都在这里分配内存,new出来的对象存放在堆中;由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了;
根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的,这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。
Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

堆分为年轻代(Eden, s0 和s1)(1/3)和老年代(2/3),配比是可以调的;
new出来的对象一般是放在堆中的Eden;
静态变量user是放在方法区中,但是它是new出来的对象,new出来的对象是放在堆中的,所以方法区中存储的是堆中user对象的内存地址;

为什么要STW?

因为如果不停止用户线线程,当你还在根据gc root往下找的时候,还没结束,此时如果用户线程结束了,那么整个堆空间都释放掉了,此时整个链路都是垃圾对象,那么这样的话GC并没什么作用,不可能在回去遍历一次,所以JVM直接STW。

1.4.4 方法区

JDK17以前方法区被称为永久代,1,8开始,使用元空间取代了永久代,元空间使用的是本地内存JVM内存之外的部分叫做本地内存);1.8之后元空间存放在堆外内存中;
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
字符串常量池在JDK7的时候放到了堆空间中
方法区中的元空间直接用的是物理内存(内存条),默认初始值是21M,当类信息很大达到21M时,会触发full gc,回收堆和方法区
方法区容量自动扩容机制,假设初始full gc后发现并没有回收多少,下次触发full gc的灵界点的大小>21M,一般元空间设置为256/512M。一定要设置值,否则大量full gc。
与永久代最大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。

1.4.5 本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务
《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定, 因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

1.4.6 JVM执行引擎

JVM中的元空间确实包含了大量的元数据,这些元数据为运行时提供了关于类、方法和字段的重要信息。但为什么在有了这么丰富的元数据之后,JVM还需要保留字节码呢?
答案就在执行引擎。执行引擎是JVM的核心部分,它负责将字节码翻译为可以在特定硬件上运行的机器代码。但是,这并不是一次性的过程。为了提高性能,JVM会使用Just-In-Time (JIT) 编译技术,将“热点”代码段编译成机器代码,从而大大加速程序的执行速度
因此,尽管元数据为JVM提供了关于类和方法的大量信息,但字节码的存在是为了允许执行引擎进行即时编译优化。这个细节不仅揭示了JVM的优雅设计,也为我们展现了Java为什么能在保持跨平台特性的同时,还能提供出色的性能。

基本结构与组件
执行引擎是JVM中的一个核心组件,负责管理和执行Java字节码。它主要由以下部分组成:
在这里插入图片描述

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值