当前位置: 首页 > article >正文

JVM详解:JVM的系统架构

计算机语言大致可以分为两类,一直是编译性语言,典型的如C++,他会先有编译器编译成可执行文件(操作系统可读,不同的操作系统需要编译成不同的可执行文件),而另一种则是翻译性语言,这种语言本身不被编译,而是由另一个已经被编译成可执行文件开启进程,在这个进程中对此计算机语言逐行的翻译执行,典型的如javaScript,其可以在node环境或浏览器环境(本质是都是v8引擎)下执行,这种语言通常都可以跨操作系统,因为操作系统的差异,其运行环境已经适应完了。

java相对特殊,其既需要编译,也需要翻译,大致过程是由java语言先编译成字节码文件,再由jvm充当翻译,来对字节码文件进行翻译执行,那么多此一举的好处是什么呢?

首先字节码本身是跨操作系统的,也就说在任何操作系统下,字节码都是一样的,所以编译成字节码对jvm的翻译工作不会造成负担,编译的过程本身能够处理掉很多无需在运行时执行的代码,比如类型检查,常量的创建等等(JVM也秉承着能在编译期处理,绝不再运行期处理的原则)。

1. JVM中的类型处理

JVM支持的数据类型可以分为原始类型引用类型,与Java语言本身一样。其中,引用类型包括整型(char 也算作整型)、浮点型、布尔类型以及 returnAddress 类型。那么,什么是 returnAddress 呢?

returnAddress 是一种特殊的类型,用于保存JVM内置指令的引用,或者可以理解为指向某个内存地址的指针。在方法调用过程中,JVM需要保存调用点的返回地址,returnAddress 就用于存储该信息,通常与堆栈操作密切相关。

JVM的设计理念是尽可能将编译期能解决的工作都交给编译器来完成。因此,类型检查类型区分这种可以在编译期间完成的事情,就是在编译阶段完成的,而不是在运行时动态执行的。例如,javac 编译器在编译Java源代码时就会根据类型生成相应的字节码。

字节码期间把类编译好不是应该的吗?不然JVM怎么知道是什么类型,换句话说JVM就不需要保存类型信息吗,不还是一样需要处理类信息吗?没错,JVM其实并不关心或者说不需要知道在运行时变量的具体类型。它只是一个翻译机器,根据字节码指令执行操作。就像操作系统运行可执行文件时,它并不关心程序的具体内容,而只按指令执行。

Boolean类型特别处理

尽管Java中有boolean类型,但JVM在内部对其的处理方式与其他基本类型不同。在JVM中,boolean类型通常被转换为 01 进行存储,boolean 数组则被转换为字节数组。


2. PC寄存器

PC寄存器(Program Counter Register)是JVM内部的重要组件,用于记录当前线程正在执行的字节码指令的位置。每个线程都有自己的PC寄存器。

  • PC寄存器的作用:它指向当前线程下一条将要执行的字节码指令。每当JVM执行字节码时,PC寄存器会自动更新,指向下一个指令。对于执行JVM内置指令时,PC寄存器会指向相应的内置指令。
  • 内置指令和系统调用:JVM内置的指令(例如,垃圾回收、同步操作等)类似于操作系统的内核函数。它们是公共资源,多个线程访问时需要进行同步控制,以确保安全执行。

JVM通过PC寄存器来控制每个线程的执行,就像操作系统通过CPU调度控制进程一样。在这种机制下,JVM能够有效避免Java程序对JVM本身的破坏,因为内置指令被严格保护。

多个Java程序(或服务)依赖于同一个JVM进程。当JVM崩溃时,所有运行在该JVM上的Java程序都会受到影响。因此,保护JVM不被非法操作是至关重要的。


3. JVM内存管理

JVM的内存管理涉及栈(Stack)和堆(Heap)两大区域,分别用于存储不同类型的数据。

3.1 栈(Stack)

每个线程的创建(java服务的主线程本质上就是jvm创建的一个翻译线程),都伴随着一个栈内存的创建,这个栈就是运行当前线程代码的内存区域,栈它的主要作用是存储局部变量和方法调用时的栈帧。那么什么是栈帧。

3.2 栈帧

当一个需要使用一个类的时候,jvm会将这个类加载进堆内存的方法区,单此时只会加载类的元数据信息,并不会将类的方法也一并加载进内存(内存中会保存类的方法的符号引用,不是真正的引用),每当类的方法被调用时,JVM会为该方法创建一个栈帧,并将栈帧放入线程的执行栈中执行。栈帧是方法执行的运行环境,其中包含了该方法的局部变量、操作数栈、常量池引用等信息。

局部变量的保存比较特殊,其并不是一个个游离在栈帧内存中,而是在编译期被保存在一个数组,并且将使用的常量变为常量的引用,存入数组,因为常量最终并不在栈帧中,而在常量池中,而原来使用局部变量的地方,都会变成数组及数组的索引。

3.3 方法区

在上文中,我们提到了方法区,方法区是堆的一部分,当一个类需要被使用时,其全部的类信息都会被加载进方法区中。常量也是类信息的一部分,所以常量池也在方法区中。常量池也很简单,就是保存常量的地方,在java被编译时,编译器会将java能够当作常量的部分都变为常量保存在一个常量区中,在加载类时其被加载进常量池保存,其中还包括一些符号引用,就不单独说了。

3.4 操作栈

任何一个方法的执行都会创建栈帧,而一个栈帧的创建也会伴随着一个操作栈的创建,操作栈是栈帧内的一个结构。它是存储方法执行中的临时数据(类似于CPU的寄存器)。在方法执行过程中,操作栈按需推送和弹出数据。它的存在使得栈帧内存的使用更加高效。局部变量的使用就是先将使用变量从数组中拿出,推入操作栈,再进行使用。

当A方法调用B方法,并接收其返回值时,如果B方法执行成功,并且返回值不需要被存在堆中,那么就会会被推入A方法的栈帧的操作数栈中,A方法继续执行。如果失败则执行异常逻辑,或直接报错。

3.5 堆(Heap)

堆是所有线程共享的内存区域。所有需要长期保存的内存数据,都将被放入堆中。

  • 堆内存是垃圾回收的主要区域。JVM会定期检查堆内存中的对象,回收那些不再被任何线程引用的对象。

与栈不同,堆内存中的数据具有较长的生命周期,可以在多个线程之间共享。

在某些情况下,栈帧会被转移到堆中。比如,当方法的返回值被另一个方法(或者栈帧)使用,并且该返回值的生命周期超过当前栈帧的生命周期时,JVM会将栈帧中的数据转移到堆中。这和JavaScript中的闭包机制基本一样,其中栈帧对应着js中的词法环境,局部变量不能被立即回收时,对应的词法环境(栈帧)会被保存下来供后续使用。

3.6 本地方法栈

在阅读java源码中,我们会发现一些方法并没有方法体,只有一个native,对于这些方法,实际上是C++或其他语言编写的,他们无法在正常的栈结构中执行,而是在jvm提供的本地方法栈中执行,这些方法也被称为本地方法。我们也可以编写自己的本地方法,在java中使用,不过这里主要讲jvm,就不讲如何编写本地方法了。本地方法栈也是堆内存的一部分

JVM的整体内存架构大概如下图所示:

在这里插入图片描述

3.7 垃圾回收

JVM的垃圾回收机制与JavaScript语言也及其类似,他是通过标记-清除的方式,来标记从根节点不可达的引用,而JavaScript是通过从根节点进行寻找不可以达引用进行回收,本质上都是清除不可达内存。

  • 标记阶段:JVM会从根节点(GC Root)开始,遍历所有可达的对象,标记所有存活的对象。
  • 清除阶段:JVM会回收未被标记的对象,释放内存。

http://www.kler.cn/a/392827.html

相关文章:

  • 文件输入输出——NOI
  • 性能测试|JMeter接口与性能测试项目
  • NAT网络工作原理和NAT类型
  • Vue 3 介绍及应用
  • 项目集章程program charter
  • 事件循环 -- 资源总结(浏览器进程模型、事件循环机制、练习题)
  • IO技术详解
  • Python3.11.9+selenium,获取图片验证码以及输入验证码数字
  • UE5 样条线组件(未完待续)
  • ILRuntime热更新通过Addressables加载DLL
  • DAY113代码审计-PHPTP框架微P系统漏审项目等
  • 初识机器学习
  • vue el-date-picker 日期选择 回显后成功后无法改变的解决办法
  • 2024年9月青少年软件编程(C语言/C++)等级考试试卷(九级)
  • Kafka基础知识学习
  • Spring Boot编程训练系统:数据管理与存储
  • Leetcode刷题笔记14
  • 时序预测:多头注意力+宽度学习
  • 2 C++ 基本内置类型
  • Vulnhub靶场案例渗透[8]- HackableII
  • 更换电脑 重新安装软件
  • 前端基础的讲解-JS(11)
  • 磁盘的物理组成(Linux网络服务器 15)
  • Kafka--关于broker的夺命连环问
  • 半导体企业如何利用 Jira 应对复杂商业变局?
  • C++进阶-->封装map和set