一图彻底搞懂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内存中的一块区域,用于存储程序运行期间产生的各种数据结构。

3. 执行引擎(Execution Engine)

• 执行引擎是JVM的核心组件之一,负责执行字节码指令。它可以采用解释器逐条解释执行字节码,也可以使用JIT(Just-In-Time)编译器将热点代码编译成本地机器码以提升性能。

4. 本地接口(Native Interface)

• 提供Java代码与操作系统交互的能力,允许Java代码通过JNI(Java Native Interface)调用本地的C/C++代码。

代码执行时JVM如何运行

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

图片

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

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 规范。同时类的加载需要符合双亲委派模型:

图片

3. 内存分配

• JVM 为 Test 类创建对应的类对象,在这个例子中,Test类的字节码信息将被加载到方法区。

• 在执行 main() 方法时,遇到 Test test = new Test(); 这一行,JVM 会在堆区为 Test 对象分配内存空间,并调用其构造函数初始化对象。Test类的一个实例(即test对象)会被分配在堆上。

4. 方法执行

• 它会在当前线程的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字节码,并提供平台无关性、自动内存管理和安全性保障等特性。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-02-05 13:46:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-05 13:46:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-05 13:46:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-05 13:46:01       20 阅读

热门阅读

  1. Rust基础拾遗--看的不多只看一篇--基础

    2024-02-05 13:46:01       32 阅读
  2. 【Golang】自定义logrus日志保存为日志文件

    2024-02-05 13:46:01       29 阅读
  3. Python判断当前运行环境是否是jupyter notebook

    2024-02-05 13:46:01       22 阅读
  4. Linux 常用命令

    2024-02-05 13:46:01       31 阅读
  5. golang 创建unix socket http服务端

    2024-02-05 13:46:01       29 阅读
  6. Pandas 条件 关键字过滤

    2024-02-05 13:46:01       27 阅读
  7. SplashScreen使用

    2024-02-05 13:46:01       30 阅读
  8. 观察者模式(Observer)

    2024-02-05 13:46:01       26 阅读
  9. 过年手机推荐

    2024-02-05 13:46:01       27 阅读
  10. 前端学习之路(2) Vue3响应式模式设计原理

    2024-02-05 13:46:01       20 阅读
  11. Redis:bigkeys内存分析

    2024-02-05 13:46:01       28 阅读
  12. php 函数三

    2024-02-05 13:46:01       24 阅读
  13. 两次NAT

    两次NAT

    2024-02-05 13:46:01      28 阅读
  14. 3.7 GNU ARM汇编语言

    2024-02-05 13:46:01       25 阅读