杰瑞科技汇

Java class文件如何运行?

  1. 编译阶段:将我们写的 .java 源代码文件编译成 .class 字节码文件。
  2. 运行阶段:在特定平台上,由 Java 虚拟机加载并执行 .class 文件。

下面我们重点讲解运行阶段,也就是 .class 文件是如何被执行的。

Java class文件如何运行?-图1
(图片来源网络,侵删)

核心概念:Java 虚拟机

.class 文件本身不能直接在操作系统上运行,它需要在一个虚拟机——也就是 Java 虚拟机——上运行。

JVM 是一个抽象的计算机,它像一台真正的计算机一样,拥有自己的指令集、内存管理、寄存器等,它的最大好处是平台无关性,你只需要为不同的操作系统(如 Windows, macOS, Linux)安装对应的 JVM,那么同一个 .class 文件就可以在任何安装了 JVM 的系统上运行,而无需重新编译。

.class 文件是 JVM 的“可执行文件”,JVM 是它的“操作系统”。


运行 .class 文件的详细步骤

当你通过命令行 java YourClassName 来运行一个 .class 文件时,背后发生了一系列复杂而精密的操作,这个过程可以分解为以下几个关键步骤:

第 1 步:启动 JVM 并加载类

当你执行 java 命令时,JVM 的启动程序会启动,它会做两件事:

  1. 找到并加载指定的类:JVM 会根据你提供的类名(YourClassName),通过类加载器 在指定的 classpath 路径下寻找对应的 .class 文件。
  2. 执行该类的 main 方法:JVM 会找到这个类的 main 方法(必须是 public static void main(String[] args) 格式),然后调用该方法,程序的执行权就从 JVM 转移到了你的 Java 代码。

第 2 步:类加载过程

JVM 不会一次性加载所有类,而是采用按需加载的策略,当一个类首次被使用时(创建实例、访问静态字段、调用静态方法),JVM 就会通过一个三步曲来加载它:

  1. 加载

    • 通过类加载器(如 Bootstrap, Extension, Application ClassLoader)从 .class 文件中读取二进制数据。
    • 将这些数据转换成方法区(JVM 内存的一块区域)内的一个数据结构,代表这个类。
    • 在内存(堆)中生成一个 java.lang.Class 对象,作为这个类的“入口”,用于后续的访问。
  2. 链接

    • 验证:确保 .class 文件的字节码流符合 JVM 规范,没有安全风险,检查魔数(CAFEBABE)、版本号、指令是否合法等,这是 JVM 的第一道安全防线。
    • 准备:为类的静态变量分配内存,并设置其默认的零值。static int count = 10; 在准备阶段,count 会被分配内存并初始化为 0,而不是 1010 的赋值在后续的初始化阶段进行。
    • 解析:将常量池内的符号引用替换为直接引用,将方法调用的符号名(如 "println")替换为指向方法在实际内存地址的指针。
  3. 初始化

    • 这是类加载的最后一步,也是真正执行类构造器 <clinit>() 方法的过程。
    • <clinit>() 方法是由编译器自动收集类中的所有静态变量的赋值动作和静态代码块中的语句合并产生的。
    • 在这个阶段,静态变量会被赋予正确的初始值。static int count = 10; count 会被赋值为 10
    • 这个过程是线程安全的,JVM 会确保一个类的 <clinit>() 方法只被线程执行一次。

第 3 步:执行引擎

当一个类被加载、链接、初始化后,JVM 的执行引擎就开始工作了,执行引擎负责执行 .class 文件中的字节码指令,它主要有三种方式来执行:

  1. 解释执行

    • 这是最传统的方式,执行引擎读取一条字节码指令,将其翻译成特定平台的机器码,然后立即执行,读完一条,再读下一条。
    • 缺点:速度慢,因为每条指令都需要即时翻译,没有缓存。
  2. 即时编译

    • 这是现代 JVM(如 HotSpot VM)采用的核心优化技术,也是 Java 性能优越的关键。
    • 工作原理:JVM 不会一开始就编译所有代码,它会先解释执行,同时监控代码的运行情况,如果一个方法被频繁调用(称为“热点代码”),JVM 就会启动 JIT 编译器,将这个方法的字节码一次性编译成与目标平台(如 Windows x64)高度优化的本地机器码。
    • 优点:一旦编译成机器码,之后每次调用该方法就直接执行机器码,速度极快,接近甚至超过 C++ 的性能。
    • JVM 还会进行各种优化,如方法内联、逃逸分析等,来进一步提升性能。
  3. 本地方法接口

    用于执行非 Java 代码编写的本地方法,通常是 C/C++ 编写的函数,这些方法主要用于与操作系统或硬件进行底层交互,例如文件 I/O、图形渲染等。

第 4 步:垃圾回收

在程序运行过程中,会不断地创建对象,这些对象都存储在 JVM 的堆内存中,当对象不再被任何引用指向时,它们就变成了“垃圾”,占用着宝贵的内存。

JVM 的垃圾回收器 会自动、周期性地扫描堆内存,回收这些垃圾对象,释放内存空间,从而避免了 C/C++ 中常见的内存泄漏问题,开发者无需手动管理内存,这是 Java 的另一大优势。


我们可以用一个流程图来清晰地展示整个过程:

graph TD
    A[程序员编写 .java 源文件] --> B{Java 编译器};
    B -- javac YourClassName.java --> C[生成 .class 字节码文件];
    C --> D{操作系统};
    D -- 启动 JVM --> E[JVM 启动];
    E -- 在 classpath 中查找 --> F[加载 .class 文件];
    F --> G{类加载器};
    G --> H[加载];
    H --> I[链接: 验证/准备/解析];
    I --> J[初始化: 执行 <clinit>()];
    J --> K[执行引擎];
    K -- 如果是热点代码 --> L[JIT 编译器];
    L -- 编译成机器码 --> M[直接执行];
    K -- 如果是非热点代码 --> N[解释执行];
    M & N --> O[程序运行];
    O --> P[对象创建在堆上];
    P --> Q[垃圾回收器];
    Q -- 回收无用对象 --> R[释放内存];
    R --> O;

实践操作:如何运行一个 .class 文件

假设你有一个 HelloWorld.java 文件。

编译

你需要使用 Java 编译器 javac.java 文件编译成 .class 文件。

# 在命令行中执行
javac HelloWorld.java

执行后,如果代码没有错误,会在同一目录下生成 HelloWorld.class 文件。

运行

使用 java 命令来运行这个 .class 文件。

重要:注意 java 命令后面跟的是类名,而不是文件名。

# 正确的命令
java HelloWorld
# 错误的命令 (会报错)
java HelloWorld.class

为什么会这样? java 命令是启动 JVM 的命令,JVM 需要知道要执行的是哪个,而不是哪个文件,JVM 会根据你提供的类名 HelloWorld,去 classpath(默认是当前目录)下寻找名为 HelloWorld.class 的文件并加载它。

设置 classpath

如果你的 .class 文件不在当前目录,你需要通过 -cp-classpath 参数来告诉 JVM 去哪里找。

你的 HelloWorld.class 文件在 C:\my_project\classes 目录下:

java -cp C:\my_project\classes HelloWorld

运行一个 Java .class 文件,本质上是启动了一个 JVM 实例,这个实例通过复杂的类加载机制将字节码加载到内存,然后由执行引擎(结合解释执行和 JIT 编译)来执行指令,同时由垃圾回收器自动管理内存,正是 JVM 的存在,实现了 Java 的“一次编写,到处运行”的跨平台特性。

分享:
扫描分享到社交APP
上一篇
下一篇