java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java编译期、运行期和半编译半解释性

java运行机制之编译期、运行期和半编译半解释性详解

作者:likerhood

编程语言一般都分为编译执行和解释执行,编译是一次性将程序翻译,然后交给机器执行,这篇文章主要介绍了java运行机制之编译期、运行期和半编译半解释性的相关资料,需要的朋友可以参考下

前言

Java 的生命周期设计之所以经典,核心在于它采用了 “半编译,半解释” 的混合模式。

要真正做到“有深度”地理解 Java 的编译期和运行期,需要深入到 前端编译(javac后端编译/执行(JIT/JVM) 的底层工作流与优化策略中。

如果从程序执行模型来看,编程语言大致可以分为三类:

Java 并不会像 C/C++ 那样直接编译为特定平台的机器码,而是先通过 javac 编译器将源码编译为 跨平台字节码(Bytecode),再交由 JVM(Java Virtual Machine)在不同平台上运行。

在运行过程中,JVM 又结合了:

两种机制。

程序启动初期通过解释执行保证快速启动;而高频运行的热点代码,则会被 JIT 编译为本地机器码并缓存,从而获得接近 C/C++ 的运行性能。

也正因为如此,Java 才真正实现了著名的:

Write Once, Run Anywhere(一次编写,到处运行)

本文从 编译期、运行期、JVM 执行机制、JIT 优化以及三种语言执行模型 等多个维度,简单梳理 Java 的运行机制与底层原理。

一、 编译期

Java 的编译期通常指的是前端编译,即由 javac 编译器将 .java 源代码文件转化为 .class 字节码文件的过程。

这个阶段的核心目标是:校验语言规范、转换语法结构,但不做深度的性能优化。

1.1 核心机制

编译期的主要工作不是优化性能,而是将 .java 源码转换为符合 JVM 规范的 .class 字节码。

javac的运行机制如下

1.2 方法重载

静态类型检查: 编译器严格按照变量的声明类型进行校验。

如果代码中有多个同名方法(方法重载 Overload),该调哪个?

class Animal {}
class Dog extends Animal {}

public class Test {
    // 【方法重载 Overload】
    public void feed(Animal a) { System.out.println("Feeding an animal..."); }
    public void feed(Dog d) { System.out.println("Feeding a dog..."); }

    public static void main(String[] args) {
        // 静态类型: Animal, 实际类型: Dog
        Animal myPet = new Dog(); 
        Test test = new Test();
        
        // 提问:这里会输出什么?
        test.feed(myPet); 
    }
}

底层执行流程:

  1. 编译期: 编译器看到 test.feed(myPet)。它检查 myPet静态类型(Animal),于是匹配到了 feed(Animal a),并将该符号引用写入字节码。
  2. 运行期(实干家): JVM 执行时,看到字节码指令要求调用 feed(Animal a),直接执行。它不会在此刻因为 myPet 实际上是个 Dog 而去反悔重选。
  3. 最终输出: Feeding an animal...

二、 运行期

运行期是 Java 最为复杂和体现技术深度的部分。 当类加载器将 .class 文件读入内存后,JVM(java虚拟机)开始接管一切。

运行期(由 JVM 和 JIT 编译器主导)它生存在一个动态的世界里,眼中只有堆内存里等号右边的真实对象。

2.1 核心机制

2.2 多态

运行期的动态特性开始显现:如何实现多态(方法重写 Override)?

// 1. 定义顶层父类
class Animal {
    public void speak() {
        System.out.println("Animal vtable: [speak() -> Animal.speak()] : 动物发出未知的叫声");
    }
}

// 2. 定义子类 Dog:【重写】了 speak 方法
class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("Dog vtable: [speak() -> Dog.speak()] : 汪汪汪!(执行了子类逻辑)");
    }
}

// 3. 定义子类 Bird:【没有重写】 speak 方法
class Bird extends Animal {
    public void fly() {
        System.out.println("鸟儿在飞翔...");
    }
    // 注意:这里没有重写 speak() 方法
}

// 4. 测试主类
public class VTableTest {
    public static void main(String[] args) {
        System.out.println("====== 多态与虚方法表测试开始 ======\n");

        /* * 场景 :JVM 无视声明类型 (Animal),直接找实际类型 (Dog)
         */
        Animal myDog = new Dog(); // 声明类型: Animal, 实际类型: Dog
        System.out.print("调用 myDog.speak() ---> ");
        // JVM 去堆内存找 myDog 的实际对象头,查 Dog 类的 vtable
        // 在 Dog 类的 vtable 中找到了重写的 speak(),直接执行!
        myDog.speak(); 

        System.out.println("--------------------------------------------------");

        /* * 场景 :顺着继承链往上找
         */
        Animal myBird = new Bird(); // 声明类型: Animal, 实际类型: Bird
        System.out.print("调用 myBird.speak() ---> ");
        // JVM 去堆内存找 myBird 的实际对象头,查 Bird 类的 vtable
        // 发现 Bird 类并没有重写 speak(),查表落空!
        // 触发机制:顺着继承链往上找,查到 Animal 类的 vtable 中有 speak(),执行父类逻辑!
        myBird.speak(); 

        System.out.println("\n====== 测试结束 ======");
    }
}

代码运行时底层流程:

1. 当执行 myDog.speak() 时:

2. 当执行 myBird.speak() 时:

三、java代码的编译和运行

3.1 编译期:Java 源码 → 跨平台字节码

对应图中绿色区域,是脱离 JVM 的前置编译环节,核心是把人类可读的 Java 源码,转为 JVM 可识别的统一标准格式。

  1. 输入:Java 源码文件(.java
  2. 核心处理:通过**javac**等 Java 编译器完成 4 步标准化操作:
    • 解析源码生成抽象语法树(AST)、填充符号表
    • 注解处理(如 Lombok 等插件的拦截增强)
    • 语义分析(常量折叠、语法校验,确保代码逻辑合法)
    • 生成字节码、解语法糖(如 foreach、泛型擦除等简化语法的还原)
  3. 输出:Java 字节码文件(.class
    • 关键特性:字节码不绑定任何操作系统、CPU 架构,是 JVM 专属的跨平台统一指令集,是 Java 实现跨平台能力的核心基础。

3.2 运行期:字节码 → 机器码 → 硬件执行

对应图中红色 JVM 区域,是代码真正执行的核心环节,全流程在 JVM 中完成,最终落地到操作系统和底层硬件。

完整执行链路如下:

  1. 类加载环节

    • .class字节码通过文件系统 / 网络进入 JVM,由类加载器完成加载→验证→准备→解析→初始化全流程,将字节码加载到 JVM 内存中,生成可执行的类结构。
  2. 混合模式执行(JVM 核心设计)

    • 类加载完成后,JVM 采用「解释执行 + JIT 编译」的双路径执行,平衡启动速度与峰值性能:

    • 解释执行:由 Java 解释器逐行翻译字节码为机器码,翻译完成立即执行。优势是启动快、无编译等待开销,保证跨平台兼容性,是程序启动初期的主要执行方式。

    • JIT 即时编译:运行时 JVM 会识别高频执行的「热点代码」(如循环、频繁调用的方法),由 C1/C2 JIT 编译器完成深度优化(方法内联、逃逸分析、标量替换等),一次性编译为本地机器码并缓存,后续执行直接调用,无需重复翻译,峰值性能无限接近 C/C++ 等纯编译型语言。

  3. 最终落地:解释器 / JIT 生成的机器码,交由操作系统调度,最终在底层 CPU 硬件上完成执行。

四、java的语言特性

4.1 纯编译型语言

提前全量静态编译,直接生成目标平台专属机器码,运行时无额外翻译,CPU可直接执行,无中间层开销。

  1. 开发阶段:编写源代码(如C的.c、C++的.cpp文件)。
  2. 全量编译阶段:通过编译器(GCC/Clang/MSVC)完成「预编译→编译→汇编→链接」,生成当前操作系统+CPU架构专属的可执行文件(Windows的.exe、Linux的ELF等),内含纯机器码。
  3. 运行阶段:操作系统直接加载可执行文件到内存,CPU逐条执行机器码,全程无翻译。

核心优缺点:

优点缺点
运行性能最快,无运行时开销跨平台性极差,需按平台单独编译
内存完全可控,无额外运行时内存占用编译耗时久,代码改动需重新编译
可做极致静态编译优化开发门槛高,需手动处理内存、指针等底层细节

代表语言与适用场景

4.2 纯解释型语言

无提前全量编译,运行时由解释器逐行翻译源代码为机器码并执行,不生成独立可执行文件,也不缓存翻译结果,灵活性高但性能牺牲大。

  1. 开发阶段:编写源代码(如早期Python的.py、Shell的.sh文件),直接分发源码。
  2. 运行阶段:目标机器需安装对应解释器;解释器逐行读取源码,实时翻译为机器码并执行,不缓存翻译结果

核心优缺点

优点缺点
极致源代码跨平台,同一份代码有解释器即可运行运行性能极差,通常比纯编译型慢10-100倍
开发效率极高,无需编译、改完即跑必须依赖解释器,无对应环境无法执行
动态性拉满,运行时可随意修改代码逻辑、变量类型无法提前静态优化,运行时才暴露语法、类型错误

代表语言与适用场景

4.3 半编译半解释型语言

为解决「编译型跨平台差、解释型性能差」的矛盾诞生,结合两者优势:先通过前端编译器将源码编译为跨平台中间码(字节码),运行时由目标平台虚拟机通过「解释执行+即时编译」混合模式转为机器码执行,实现“一次编写,到处运行”。

分为编译期运行期两大阶段:

  1. 编译期(一次编写的核心)

    • 开发阶段:编写Java源代码(.java文件)。

    • 前端编译:通过javac编译器将源码一次性编译为跨平台字节码文件(.class)

      • 关键特性:字节码不绑定操作系统、CPU架构,是JVM专属统一指令集,同一份字节码在所有平台一致,是跨平台核心基础。
  2. 运行期(到处运行的核心)

    • 目标机器需安装对应平台的JVM虚拟机(Windows/Linux/macOS均有专属JVM,适配底层系统和硬件)。

    • 类加载:JVM把.class字节码加载到内存,完成验证、准备、解析等流程。

    • 混合模式执行(平衡启动速度与峰值性能):

      • 解释执行:程序启动初期,逐行翻译字节码为机器码执行,保证快速启动与跨平台兼容。
      • JIT即时编译:运行时识别高频「热点代码」(如循环、频繁调用的方法),一次性全量编译为本地机器码并缓存,后续执行直接调用,无需重复翻译,峰值性能无限接近纯编译型语言。

核心优缺点

优点缺点
真正跨平台:同一份字节码,有对应JVM即可运行,无需按平台修改、重编译有启动开销:JVM启动、类加载、解释执行有固定开销,不适合超短生命周期程序(如简单脚本)
高性能:JIT即时编译优化,热点代码性能接近C/C++,远超纯解释型语言强依赖运行环境:目标机器需安装对应版本JVM,原生无法生成独立可执行文件(GraalVM可实现,非原生能力)
内存安全、开发效率高:JVM自带GC自动回收内存,无需手动管理,规避内存泄漏、野指针问题内存占用更高:JVM本身、GC、运行时数据区均有固定内存开销
动态能力强:基于字节码和JVM实现反射、动态代理,是Spring等企业级框架的核心支撑底层控制能力有限:不适合操作系统内核、驱动等硬件级开发

代表语言与适用场景

4.4 常见误区

  1. 现代Python/JavaScript并非纯解释型语言:CPython会预编译为.pyc字节码,Chrome V8、PyPy均引入JIT编译,本质已属于半编译半解释模型。
  2. Go/Rust并非半编译型语言:二者虽带GC、协程调度等运行时能力,但均提前全量编译为目标平台机器码,运行时无翻译环节,属于纯编译型语言。
  3. Java并非固定半编译半解释模式:现代JVM默认「解释+JIT」混合模式,可通过参数强制纯解释/纯编译;GraalVM支持提前将Java代码编译为本地机器码,转为纯编译型执行。

总结

Java 的运行机制,本质上是一套围绕 “跨平台 + 高性能” 而设计的动态执行体系。

与传统纯编译型语言不同,Java 并不会直接生成特定平台的机器码,而是采用:**“源码 → 字节码 → JVM → 机器码”**的分层执行模型。

它的本质是:

先编译为跨平台字节码,再由 JVM 动态解释与 JIT 编译执行。

因此:

最终形成了 Java 最经典的设计哲学:

一次编写,到处运行(Write Once, Run Anywhere)

到此这篇关于java运行机制之编译期、运行期和半编译半解释性详解的文章就介绍到这了,更多相关java编译期、运行期和半编译半解释性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文