Java虚拟机——JVM(Java Virtual Machine)解析一

1.JVM是什么?

1.1 JVM概念

Java Virtual Machine (JVM) 是JDK的核心组件之一,它使得 Java 程序能够在任何支持 JVM 的设备或操作系统上运行,而无需修改源代码

JDK是什么,JDK和JVM是什么关系?

在这里插入图片描述
1.Java IDE(Integrated Development Environment):集成开发环境,专门用于Java编程(例如IDEA),开发Java应用程序前需要选择某具体版本的JDK
在这里插入图片描述
2.JDK(Java Development Kit)
Java开发工具包,用于编写,编译,调试和运行Java应用程序,提供了开发Java程序所需的所有工具和资源。JDK=JRE+其他
3.JRE(Java Runtime Environment)
Java运行时环境,包含Java虚拟机(JVM),类库和其他文件,用于运行Java程序
通过以上的内容梳理可知,JDK包含JRE,JRE包含JVM

1.2 JVM作用

说到这里,我就要搞清楚计算机是如何认识我们编写的代码的
首先,计算机只认识0和1,执行的指令集也是一串一串的01。所以,我们编写的代码一定是被转换为二进制文件才能被计算机执行。
其次,不同的操作系统的指令集是不同的。例如,在Windows系统中0000 0010的意思是加法,而在Linux系统中的意思是减法(只是举例,不是真正的指令集)。
那么将开发者编写的代码直接转换为二进制文件,放在不同的操作系统中运行的结果可能也不同,如果要实现同样的功能就需要在不同的操作系统上编写不同的代码。
Java能跨平台运行的原因
先回忆一下刚开始学习Java编程的时候听过的一句话"一次编译,到处运行",这句话体现了Java的跨平台能力,开发者只需要编写一次Java代码并编译成字节码文件,就可以在任何安装了JVM的机器上执行。
下面是Java代码从编写到运行的过程
在这里插入图片描述

java文件通过javac(Java编译器,Java Compiler)编译成字节码文件(class文件)
可以把JVM看成计算机,字节码文件就相当于JVM的指令集。然后JVM把字节码文件转换为对应系统的指令集,
例如:现在有"Hello World"这么一串代码,Windows系统上的JVM将代码转换为0010,Linux系统上的JVM将代码转换为0110,最后两个系统的执行结果都是"Hello World",这就实现了Java程序的跨平台运行

1.3 JVM执行流程

在这里插入图片描述
程序在执行之前先要把java代码转换成字节码(class文件),JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader) 把文件加载到内存中运行时数据区(Runtime Data Area) ,而字节码文件是 JVM 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine)将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口本地库接口(Native Interface) 来实现整个程序的功能

2.深入学习JVM

2.1运行时数据区

运行时数据区是Java程序执行时所需的内存区域。JVM启动时,会根据不同的内存区域分配管理内存。一般来说,线程共享的区域的生命周期和JVM一致;线程私有的区域会随着线程的创建和销毁跟着一起创建和销毁,生命周期和所属线程一致。
在这里插入图片描述

2.1.1.方法区(线程共享)

方法区是JVM内存规范中定义的抽象概念,并不是真实的物理空间。

在JDK8以前,使用永久代来实现方法区。永久代是在堆中开辟的内存空间,主要存储:

(1)类的元信息:类名,修饰符等
(2)常量池
(3)静态变量
注:永久代的大小是在JVM启动时固定的,难以根据实际需求来动态调整,而且永久代是在堆内存中开辟的空间,这也限制了永久代的大小。所以在JDK8之后使用元空间来实现方法区

在JDK8及以后,使用元空间来实现方法区(上面图片中的元数据区/元空间不太准确,但我确实没有找到更好的图片)

元空间存储方式相较于永久代有所改动。首先,元空间不再在堆内存中开辟空间,而是单独向操作系统申请空间,这就不会再受到堆内存大小的限制。其次,元空间可以根据实际需求来动态调整大小。然后,元空间内部存储的数据也发生了一些变化
(1)类的元信息依旧存储在元空间中
(2)常量池转移到堆中
(3)静态变量
在这里插入图片描述

2.1.2.堆(线程共享)

JVM内存中最大的部分,是所有线程共享的空间。
1.在JVM启动时创建(可以动态调整大小,有上限),是垃圾回收的主要位置。
2.几乎所有的对象实例(通过new创建)都存储在堆中
3.从内存回收角度来看java堆可分为:新生代和老生代
注:JVM在编译时会分析对象是否逃逸出方法或者线程,如果对象不会逃逸出方法或者线程,只在内部使用,JVM就可以将其分配在栈(线程私有)上,以提高性能减少垃圾回收的开销,这里不做详细讨论

2.1.3.虚拟机栈(线程私有)

每个线程都有一个独立的虚拟机栈,用于存储栈帧(Stack Frame)。每个方法在执行时都会创建一个栈帧,用于存储方法的局部变量,方法调用和返回地址

2.1.4.本地方法栈(线程私有)

存储本地方法(Native Methods)调用的信息。本地方法是使用非Java语言(如C、C++)编写的方法,它们通过JNI(Java Native Interface,Java本地方法接口)与Java代码进行交互

2.1.5.程序计数器(线程私有)

存储当前线程执行的字节码指令的地址

2.2类加载器

2.2.1类加载过程

类加载包括:加载(Loading),连接(Linking)和初始化(Initialization)三个步骤
在这里插入图片描述

(1)加载(Loading)

通过类加载器将硬盘中的字节码文件加载到运行时数据区,并生成一个类对象存储在方法区。当然,也不一定是硬盘中的字节码文件,还可能来自于网络、数据库,甚至是即时生成的字节码文件
在这里插入图片描述
注意一:着重区分加载和类加载的区别。加载(Loading)只是类加载的第一个阶段;而类加载包括加载(Loading),连接(Linking)和初始化(Initialization)三个步骤

(2)验证(Verification)

确保类文件符合JVM规范中定义的类文件格式。
文件格式验证:检查文件是否是以0xCAFEBABE开头,这是Java类文件的标识;检查类文件的版本号是否和JVM对应,Java 8的JVM不支持Java 9的类文件
元数据验证:确保类的元数据信息没有语法错误
字节码验证:确保类的字节码指令是合法的,不会导致JVM崩溃或者执行不安全操作
注意二:在今天,验证操作不单单是验证(Verification)这一个阶段了。在解析阶段还有符号引用验证,解析阶段可以发生在初始化之前,也可能发生在初始化之后(代码中发生多态来实现后期绑定),而且JVM的开发人员还在不断完善验证策略,所以验证操作分散在各个阶段内,并不是单一的阶段。

(3)准备(Preparation)

为类的静态变量分配内存并设置默认值。

  1.  将类的静态变量分配到方法区(有一些静态变量不在方法区)
    
  2.  基本数据类型初始化默认值,int类型初始化为0,boolean类型初始化为false
    
  3.  引用类型初始化为null
    
  4.  如果是静态常量,直接赋目标值,跳过默认值
    
(4)解析(Resolution)

将符号引用替换为直接引用。
直接引用:指向内存中的实际地址的指针或者偏移量
符号引用:是一种文本形式的引用,使用字符串或其他符号来描述目标类,字段和方法
问题:为什么要引入符号引用?
因为在class文件加载到运行时数据区之前,class文件是在硬盘或者其他空间中存储的(反正不是内存),没有地址和指针这个概念,如果要定位一个类,只能使用其他形式的标识符
在这里插入图片描述
动态解析举例:加入B类是一个抽象类,实现的是身份选择功能,C和D类继承了B,分别代表普通用户和管理员。那么A到底引用C还是D,这可能需要用户来决定。此时,A类就会先进行初始化阶段,当用户选择完身份后再来解析

(5)初始化(Initialization)

任务:执行类的初始化代码
触发条件(以下任一情况都会触发初始化):
1.创建类的实例(new
2.访问类的静态变量(非final)或静态方法
3.反射调用类(Class.forName("com.example.MyClass")
4.子类初始化时,其父类会先被初始化
5. 作为程序入口的主类(包含main()方法的类)
执行顺序
1.父类静态变量和静态代码块(按代码顺序执行)
2.子类静态变量和静态代码块(按代码顺序执行)
3.父类实例变量和构造代码块
4.父类构造函数
5.子类实例变量和构造代码块
6.子类构造函数

2.2.2类加载器

在上述类加载过程中,第一个阶段"加载"涉及到JVM中一个非常重要的模块——类加载器。类加载器主要负责根据类的全限定名找到对应的.class文件
在这里插入图片描述

什么是全限定名?
全限定名指的是包含**包名**在内的**类**的完整名称。例如,假设有一个ArrayList类,属于java.util包,那么它的全限定名就是java.util.ArrayList
类加载器的搜索范围:不同的类加载器负责不同路径的类加载。在JVM中,不算自定义的类加载器,默认的类加载器有三种:

(1) 启动/引导类加载器(Bootstrap ClassLoader):加载 `JAVA_HOME/lib` 下的核心类库
(如 `rt.jar`)

注:这里的JAVA_HOME一般指的是JDK的安装路径,如下图
在这里插入图片描述

rt.jar(以JDK8为例):包含Java标准库,如java.lang,java.util等,至于标准库有哪些在Java语言规范中有明确规定,这里不过多赘述。这些标准库中的类由启动/引导类加载器负责加载。

(2) 扩展类加载器(Extension ClassLoader):加载 `JAVA_HOME/lib/ext` 下的扩展类

Java语言规范中没有的类,并且是由JVM开发者添加的类,称为扩展类,这些类由扩展类加载器负责加载。JVM的版本有很多,所以扩展类有哪些和JVM的具体版本有关

(3) 应用类加载器(Application ClassLoader):加载用户类路径(ClassPath)下的类

一般包括开发者编写的类和第三方依赖库

2.2.3 双亲委派机制(不考虑自定义类加载器)

核心思想:当类加载器收到类加载请求时,不会自行立即加载,而是先将该加载请求委派给父类加载器,最终请求会到达顶层类加载器。

完整过程:

(1)顶层加载器(启动类加载器,Bootstrap ClassLoader)检查JAVA_HOME/lib路径下的核心类库,如果能找到就加载
(2)如果启动类加载器找不到,请求返回给扩展类加载器,检查JAVA_HOME/lib/ext路径下的扩展类,如果能找到就加载
(3)如果扩展类加载器找不到,请求返回给应用类加载器,检查用户类路径下的类
(4)如果所有类加载器均无法加载请求类,则抛出ClassNotFoundException

双亲委派机制的优势:

1. 避免核心类被篡改
安全性:通过优先由启动类加载器加载核心类(如 java.lang.String),确保用户无法定义同名类覆盖核心类
示例:若用户自定义 java.lang.String,JVM 会直接加载核心库中的版本,用户类被忽略
2.防止重复加载
唯一性:每个类由父类优先加载,确保同一个类在多个类加载器中只加载一次
示例:若父类已加载 com.example.MyClass,子类不会再重复加载,避免内存浪费和类冲突
3.天然的类隔离性
隔离性:不同类加载器加载的类属于不同的命名空间,天然隔离。
4.灵活扩展
可定制性:允许子类加载器扩展加载范围(如从网络、数据库加载类),同时不破坏核心类的稳定性

3.小结

下篇博文将继续介绍JVM剩下核心机制——垃圾回收

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/900527.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初识 Three.js:开启你的 Web 3D 世界 ✨

3D 技术已经不再是游戏引擎的专属,随着浏览器技术的发展,我们完全可以在网页上实现令人惊艳的 3D 效果。而 Three.js,作为 WebGL 的封装库,让 Web 3D 的大门向更多开发者敞开了。 这是我开启这个 Three.js 专栏的第一篇文章&…

OpenGL ES -> SurfaceView + EGL实现立方体纹理贴图+透视效果

XML文件 <?xml version"1.0" encoding"utf-8"?> <com.example.myapplication.MySurfaceView xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"…

pikachu靶场搭建教程,csfr实操

靶场安装 靶场下载地址 百度网盘下载地址和密码 百度网盘 请输入提取码 0278 github靶场下载地址 https://gitcode.com/Resource-Bundle-Collection/c7cc1 安装前提 这两个文件夹的配置文件都要进行更改修改数据库密码 D:\phpstudy_pro\WWW\pikachu\inc D:\phpstudy_pro…

浙江大学DeepSeek系列专题线上公开课第二季第四期即将上线!端云协同:让AI更懂你的小心思! - 张圣宇 研究员

今晚8点10分左右&#xff0c;端云协同&#xff1a;让AI更懂你的小心思&#xff01;浙大学者张圣宇研究员将揭秘人机交互新玩法。浙江大学DeepSeek系列专题线上公开课第二季第四期即将上线&#xff01; 讲座 主题&#xff1a; 大小模型端云协同赋能人机交互 主讲人&#xff1a…

Vue3实战三、Axios封装结合mock数据、Vite跨域及环境变量配置

目录 Axios封装、调用mock接口、Vite跨域及环境变量配置封装Axios对象调用mock接口数据第一步、安装axios&#xff0c;处理一部请求第二步、创建request.ts文件第三步、本地模拟mock数据接口第四步、测试axiosmock接口是否可以调用第五步、自行扩展 axios 返回的数据类型 axios…

Linux如何删除文件名包含无效编码字符文件

在Linux中&#xff0c;文件名包含无效编码字符或特殊不可见字符时&#xff0c;可能导致此文件无法通过常规方式选中或删除&#xff0c;可以通过下面方法处理 1、确认文件名问题 检查终端编码环境 echo $LANG # 默认应为 UTF-8&#xff08;如 en_US.UTF-8&#xff09; 查看…

Completablefuture的底层原理是什么

参考面试回答&#xff1a; 个人理解 CompletableFuture 是 Java 8 引入的一个类、它可以让我们在多线程环境中更加容易地处理异步任务。CompletableFuture 的底层原理是基于一个名为 FutureTask 的机制、结合了 监听器模式 和 等待-通知机制 来处理异步计算。 1.首先就是Com…

C/C++ 调用约定:深入理解栈与平栈

前言 在编程中&#xff0c;理解函数调用约定和栈的机制对于编写高效代码、调试程序以及进行逆向工程至关重要。本文将深入探讨 C 和 C 的调用约定&#xff0c;以及栈与平栈的相关知识。 C 调用约定 在 C 语言中&#xff0c;默认的调用约定是 cdecl。cdecl 调用约定的特点如下&…

xv6-labs-2024 lab1

lab-1 注&#xff1a;实验环境在我的汇编随手记的末尾部分有搭建教程。 0.前置 第零章 xv6为我们提供了多种系统调用&#xff0c;其中&#xff0c;exec将从某个文件里读取内存镜像(这确实是一个好的说法)&#xff0c;并且将其替换到调用它的内存空间&#xff0c;也就是这个…

属性修改器 (AttributeModifier)

主页面设置组件 import { MyButtonModifier } from ../datastore/MyButtonModifier;Entry ComponentV2 struct MainPage {// 支持用状态装饰器修饰&#xff0c;行为和普通的对象一致Local modifier: MyButtonModifier new MyButtonModifier();build() {Column() {Button(&quo…

【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的监控:使用 Actuator 实现健康检查

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、引子&…

类和对象(下篇)(详解)

【本节目标】 1. 再谈构造函数 2. Static成员 3. 友元 4. 内部类 5. 再次理解封装 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 #include <iostream> using name…

高精度算法

高精度加法 输入两个数&#xff0c;输出他们的和&#xff08;高精度&#xff09; 输入样例 111111111111111111111111111111 222222222222222222222222222222 输出样例 333333333333333333333333333333 #include <bits/stdc.h> using namespace std;string a,b; in…

Linux开发中注意哪些操作系统安全

在 Linux 开发中&#xff0c;确保操作系统的安全至关重要。以下是一些需要注意的方面&#xff1a; 用户管理与权限控制 合理设置用户权限&#xff1a;为不同的用户和用户组分配适当的权限&#xff0c;遵循最小权限原则。避免给普通用户过多的权限&#xff0c;以免他们误操作或…

x64dbg调试python解释器

可以先写个input()这会让dbg中断在ntdll模块中&#xff0c;查看调用堆栈在系统调用结束后的打断点 然后直接断到PyObject_Vectorcall函数

✅ Ultralytics YOLO验证(Val)时自动输出COCO指标(AP):2025最新配置与代码详解 (小白友好 + B站视频)

✅ YOLO获取COCO指标(3)&#xff1a;验证(Val) 启用 COCO API 评估&#xff08;自动输出AP指标&#xff09;| 发论文必看&#xff01; | Ultralytics | 小白友好 文章目录 一、问题定位二、原理分析三、解决方案与实践案例步骤 1: 触发 COCO JSON 保存步骤 2: 确保 self.is_coc…

【嵌入式学习3】基于python的tcp客户端、服务器

目录 1、tcp客户端 2、tcp服务器 3、服务器多次连接客户端、多次接收信息 1、tcp客户端 """ tcp:客户端 1. 导入socket模块 2. 创建socket套接字 3. 建立tcp连接(和服务端建立连接) 4. 开始发送数据(到服务端) 5. 关闭套接字 """ import soc…

Linux: network: 两台直连的主机业务不通

前提环境,有一个产品的设定是两个主机之间必须是拿网线直连。但是设备管理者可能误将设置配错,不是直连。 最近遇到一个问题,说一个主机发的包,没有到对端,一开始怀疑设定的bond设备的问题,检查了bond的设置状态,发现没有问题,就感觉非常的奇怪。后来就开始怀疑两个主机…

COMSOL固体力学接触

目录 一、接触非线性及接触面积计算 一、接触非线性及接触面积计算 COMSOL接触非线性及接触面积计算_哔哩哔哩_bilibili 形成联合体&#xff0c;在定义处右键选择“建立接触对” 位移dz使用参数化扫描。 接触选择定义中设置的接触对&#xff0c;选择罚函数&#xff0c;摩擦设置…

22.OpenCV轮廓匹配原理介绍与使用

OpenCV轮廓匹配原理介绍与使用 1. 轮廓匹配的基本概念 轮廓匹配&#xff08;Contour Matching&#xff09;是计算机视觉中的一种重要方法&#xff0c;主要用于比较两个轮廓的相似性。它广泛应用于目标识别、形状分析、手势识别等领域。 在 OpenCV 中&#xff0c;轮廓匹配主要…