首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java虚拟机——JVM(Java Virtual Machine)解析一

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

作者头像
用户11873138
发布2026-01-13 21:30:41
发布2026-01-13 21:30:41
1170
举报

1.JVM是什么?

1.1 JVM概念

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

代码语言:javascript
复制
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内存规范中定义的抽象概念,并不是真实的物理空间。

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

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

代码语言:javascript
复制
在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)三个步骤

在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
(1)加载(Loading)

通过类加载器将硬盘中的字节码文件加载到运行时数据区,并生成一个类对象存储在方法区。当然,也不一定是硬盘中的字节码文件,还可能来自于网络、数据库,甚至是即时生成的字节码文件

在这里插入图片描述
在这里插入图片描述

注意一:着重区分加载和类加载的区别。加载(Loading)只是类加载的第一个阶段;而类加载包括加载(Loading),连接(Linking)和初始化(Initialization)三个步骤

代码语言:javascript
复制
(2)验证(Verification)

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

代码语言:javascript
复制
(3)准备(Preparation)

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

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

将符号引用替换为直接引用。 直接引用:指向内存中的实际地址的指针或者偏移量 符号引用:是一种文本形式的引用,使用字符串或其他符号来描述目标类,字段和方法 问题:为什么要引入符号引用? 因为在class文件加载到运行时数据区之前,class文件是在硬盘或者其他空间中存储的(反正不是内存),没有地址和指针这个概念,如果要定位一个类,只能使用其他形式的标识符

在这里插入图片描述
在这里插入图片描述

动态解析举例:加入B类是一个抽象类,实现的是身份选择功能,C和D类继承了B,分别代表普通用户和管理员。那么A到底引用C还是D,这可能需要用户来决定。此时,A类就会先进行初始化阶段,当用户选择完身份后再来解析

代码语言:javascript
复制
(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中,不算自定义的类加载器,默认的类加载器有三种:

代码语言:javascript
复制
(1) 启动/引导类加载器(Bootstrap ClassLoader):加载 `JAVA_HOME/lib` 下的核心类库
(如 `rt.jar`)

注:这里的JAVA_HOME一般指的是JDK的安装路径,如下图

在这里插入图片描述
在这里插入图片描述

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

代码语言:javascript
复制
(2) 扩展类加载器(Extension ClassLoader):加载 `JAVA_HOME/lib/ext` 下的扩展类

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

代码语言:javascript
复制
(3) 应用类加载器(Application ClassLoader):加载用户类路径(ClassPath)下的类

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

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

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

代码语言:javascript
复制
完整过程:

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

代码语言:javascript
复制
双亲委派机制的优势:

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

3.小结

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.JVM是什么?
    • 1.1 JVM概念
    • 1.2 JVM作用
    • 1.3 JVM执行流程
  • 2.深入学习JVM
    • 2.1运行时数据区
      • 2.1.1.方法区(线程共享)
      • 2.1.2.堆(线程共享)
      • 2.1.3.虚拟机栈(线程私有)
      • 2.1.4.本地方法栈(线程私有)
      • 2.1.5.程序计数器(线程私有)
    • 2.2类加载器
      • 2.2.1类加载过程
      • 2.2.2类加载器
      • 2.2.3 双亲委派机制(不考虑自定义类加载器)
  • 3.小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档