一图看懂JVM执行过程

JVM知识回顾

我们都知道JVM(Java虚拟机)主要划分为以下几个部分:

  1. 类加载子系统(Class Loader Subsystem)

    • 类加载器负责查找和加载类文件(.class文件),它是JVM执行的第一步。类加载器按照类的全限定名定位类文件,将二进制数据流转化为方法区内的运行时数据结构,并最终创建 java.lang.Class 类的实例。
  2. 运行时数据区(Runtime Data Areas)

  • 程序计数器(Program Counter Register) :每个线程都有一个独立的PC寄存器,用来记录当前线程所执行的字节码指令地址。
  • 虚拟机栈(VM Stack) :每个线程拥有一个私有的栈,用于存储方法调用时的局部变量、操作数栈和动态链接信息等,每一个方法调用都会对应一个栈帧(Stack Frame)。
  • 本地方法栈(Native Method Stack) :与虚拟机栈类似,但服务于 native 方法(非Java语言实现的方法)。
  • 堆(Heap) :所有线程共享的内存区域,主要用于存储对象实例和数组。
  • 方法区(Method Area)/ 元空间(Metaspace)(JDK 1.8 及以后) :存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 运行时数据区是JVM内存中的一块区域,用于存储程序运行期间产生的各种数据结构。
  1. 执行引擎(Execution Engine)
  • 执行引擎是JVM的核心组件之一,负责执行字节码指令。它可以采用解释器逐条解释执行字节码,也可以使用JIT(Just-In-Time)编译器将热点代码编译成本地机器码以提升性能。
  1. 本地接口(Native Interface)
  • 提供Java代码与操作系统交互的能力,允许Java代码通过JNI(Java Native Interface)调用本地的C/C++代码。

代码执行时JVM如何运行

相信大家对上面这部分很熟悉,但是具体代码在执行过程中分别是怎么对应的呢?

下面我们通过一段代码来看下:

Java 复制代码
public class Test{
        public void hello(int a){
                 int b = a+1;
        }
        public static vid main(String args){
                 Test test = new Test();
                 test.hello(1);
        }
) 

这段 Java 代码类 Test 中提供函数 hello() 和主入口函数 main() 的定义和使用。当执行这个程序时,JVM(Java 虚拟机)会按照以下步骤来执行这段代码:

  1. 编译阶段

    • 首先,通过 Java 编译器(如 javac)将这段源代码编译成 .class 字节码文件。将会生成 Test.class 文件。大家通过开发工具运行时自动帮我们编译了。
  2. 类加载阶段

  • 当程序启动并执行 Test 类的 main() 方法时,JVM 的类加载器会找到 Test.class 文件,并将其加载到 JVM 中。

  • 加载过程中,类加载器会验证字节码文件的结构、元数据和符号引用,确保它们符合 JVM 规范。同时类的加载需要符合双亲委派模型:

  1. 内存分配
  • JVM 为 Test 类创建对应的类对象,在这个例子中,Test类的字节码信息将被加载到方法区。
  • 在执行 main() 方法时,遇到 Test test = new Test(); 这一行,JVM 会在堆区为 Test 对象分配内存空间,并调用其构造函数初始化对象。Test类的一个实例(即test对象)会被分配在堆上。
  1. 方法执行
  • 它会在当前线程的Java方法栈上为 hello() 方法创建一个新的栈帧,用于存放局部变量表、操作数栈和其他运行时信息。

  • 局部变量表中分配空间给形参 a 并初始化为传入的值 1。

  • 根据字节码指令计算 a+1,并将结果赋值给局部变量 b

  • 执行完毕后,hello() 方法完成其逻辑且无返回值,因此栈帧被弹出,控制权返回到 main() 方法的栈帧。

  • 如果程序中使用了 native 方法(本地方法),那么本地方法栈将会涉及到,但在这里并没有使用 native 方法。

  • 当执行到 test.hello(1); 时,JVM 查找 hello() 方法的字节码指令并开始执行。

  • 下面是运行时数据区的结构:

这里是一段代码,当是一个服务时就涉及运行时数据区的垃圾回收了,之前的文章有过介绍,参考:面试官问我JVM如何找到垃圾!

下面是常见JVM参数的设置:

  • -Xms:设置堆的初始大小。

  • -Xmx:设置堆的最大大小。

  • -Xmn:设置年轻代的大小。

  • -Xss:设置线程栈的大小。

  • -XX:PermSize 和 -XX:MaxPermSize:设置永久代的初始大小和最大大小(在Java 8及之前的版本中使用)。

  • -XX:MaxMetaspaceSize:设置元空间的最大大小(在Java 8及之后的版本中替代了永久代)。

  • -XX:NewRatio:设置年轻代与老年代的比例。

  • -XX:MaxTenuringThreshold:设置对象在年轻代中经过多少次垃圾回收后进入老年代。

  • -XX:SurvivorRatio:设置Eden区和两个Survivor区的比例。

  • -XX:+UseParallelGC 或 -XX:+UseConcMarkSweepGC 等:选择垃圾回收器。

至此我们通过一段代码知道这些JVM组件共同协作,使得JVM能够加载、验证、执行Java字节码,并提供平台无关性、自动内存管理和安全性保障等特性。

相关推荐
顽石九变8 分钟前
【SpringBoo3】SpringBoot项目Web拦截器使用
spring boot·后端
小韩学长yyds8 分钟前
Java调用第三方HTTP接口:从入门到实战
java·开发语言·http
苏十八10 分钟前
JavaEE Servlet02
java·服务器·网络·java-ee·json
爬菜15 分钟前
异常(5)
java
梦兮林夕25 分钟前
从零掌握 Gin 参数解析与验证
后端·go·gin
bobz96535 分钟前
IPSec IKE PSK 与扩展支持Xauth账户密码
后端
supermodule35 分钟前
基于flask的一个数据展示网页
后端·python·flask
苹果酱056739 分钟前
Golang的数据库备份与恢复
java·vue.js·spring boot·mysql·课程设计
315356691344 分钟前
manus邀请码申请手把手教程
前端·后端·面试
青石路1 小时前
经由同个文件多次压缩的文件MD5都不一样问题排查,感慨AI的强大!
java·后端