Java程序的执行过程:从编译到垃圾回收,一文读懂Java程序的生命周期

你是否曾经好奇过当你编写一段Java代码并运行它时,背后究竟发生了什么?Java程序的执行过程似乎神秘而复杂,但实际上,它遵循着一系列精心设计的步骤。本文将为你揭开Java程序执行的神秘面纱,带你深入了解从源代码到最终运行的整个过程。
image.png

目录

    • Java程序执行的生命周期
    • 编写源代码:一切的起点
    • 编译过程:从.java到.class
    • 类加载:JVM的核心机制
    • 字节码验证:确保代码安全
    • 即时编译:性能的秘密武器
    • 程序执行:finally,代码开始运行
    • 垃圾回收:自动内存管理
    • 深入案例分析:一个完整的Java程序执行过程
    • 常见问题与优化技巧
    • 总结与展望

Java程序执行的生命周期

Java程序的执行过程是一个复杂而精密的过程,涉及多个阶段和组件。从编写源代码到程序最终运行,Java程序经历了以下主要阶段:

  1. 编写源代码
  2. 编译源代码
  3. 类加载
  4. 字节码验证
  5. 即时编译(JIT)
  6. 程序执行
  7. 垃圾回收

接下来,我们将详细探讨每个阶段,揭示Java程序执行的内部机制。
image.png

编写源代码:一切的起点

Java程序的执行过程始于编写源代码。作为开发者,我们使用文本编辑器或集成开发环境(IDE)创建以.java为扩展名的源文件。这些文件包含了符合Java语法规则的代码。

例如,一个简单的Java程序可能如下所示:

public class HelloWorld {public static void main(String[] args) {System.out.println("Hello, World!");}
}

这段代码定义了一个名为HelloWorld的类,其中包含一个main方法,这是Java程序的入口点。
image.png

编译过程:从.java到.class

一旦源代码编写完成,下一步就是编译。编译是将人类可读的源代码转换为机器可执行的字节码的过程。在Java中,这个过程由Java编译器(通常是javac)完成。

编译过程包括以下步骤:

  1. 词法分析:编译器将源代码分解成一系列的标记(tokens)。
  2. 语法分析:这些标记被组织成一个抽象语法树(AST),表示程序的结构。
  3. 语义分析:编译器检查程序的语义正确性,如类型检查。
  4. 字节码生成:最后,编译器生成Java字节码,保存在.class文件中。

使用上面的HelloWorld.java示例,我们可以使用以下命令编译它:

javac HelloWorld.java

这将生成一个HelloWorld.class文件,其中包含了Java虚拟机(JVM)可以理解的字节码。

字节码是一种中间代码,它不是直接的机器码,而是一种特殊的指令集,专门为JVM设计。这就是Java “一次编写,到处运行” 理念的关键所在。
image.png

类加载:JVM的核心机制

当我们运行Java程序时,JVM启动并开始加载必要的类。类加载是Java程序执行过程中的一个关键阶段,它负责将编译后的字节码加载到JVM中。

类加载过程包括以下三个主要步骤:

  1. 加载:通过类的全限定名找到对应的字节码文件,并将其读入内存。
  2. 链接
    • 验证:确保加载的字节码符合JVM规范。
    • 准备:为类的静态字段分配内存并设置初始值。
    • 解析:将符号引用转换为直接引用。
  3. 初始化:执行类构造器<clinit>()方法,初始化静态字段。
    image.png
    类加载器是实现类加载机制的核心组件。Java使用了双亲委派模型来组织类加载器,主要包括以下几种:
  • 启动类加载器(Bootstrap ClassLoader)
  • 扩展类加载器(Extension ClassLoader)
  • 应用程序类加载器(Application ClassLoader)
  • 自定义类加载器

以下是一个简单的示例,展示如何查看一个类的类加载器:

public class ClassLoaderDemo {public static void main(String[] args) {System.out.println(ClassLoaderDemo.class.getClassLoader());System.out.println(String.class.getClassLoader());}
}

输出可能如下:

sun.misc.Launcher$AppClassLoader@18b4aac2
null

这里,ClassLoaderDemo类由应用程序类加载器加载,而String类由引导类加载器加载(显示为null)。

字节码验证:确保代码安全

字节码验证是Java安全模型的重要组成部分。它发生在类加载的链接阶段,目的是确保加载的字节码不会破坏JVM的安全性。
image.png

验证过程包括以下几个方面:

  1. 格式检查:确保字节码文件的基本格式正确。
  2. 语义检查:验证字节码的语义是否符合Java语言规范。
  3. 字节码验证:检查实际的字节码指令序列是否合法。
  4. 符号引用验证:确保符号引用可以被正确解析。

以下是一个简单的示例,展示了非法字节码可能导致的问题:

public class IllegalBytecodeDemo {public static void main(String[] args) {int x = 1;int y = 0;int z = x / y;  // 这里会产生ArithmeticException}
}

虽然这段代码在编译时不会报错,但在运行时会抛出除零异常。字节码验证不会捕获这种运行时错误,但它会确保字节码本身是合法的,不会导致JVM崩溃。

即时编译:性能的秘密武器

image.png

即时编译(JIT)是Java性能优化的关键技术之一。JIT编译器在运行时将热点字节码编译成本地机器码,从而显著提高程序的执行速度。

JIT编译的过程大致如下:

  1. JVM首先以解释模式执行字节码。
  2. 同时,JVM监控代码的执行情况,识别热点代码(频繁执行的代码段)。
  3. 一旦发现热点代码,JIT编译器就会将其编译成本地机器码。
  4. 后续执行时,JVM直接运行编译后的本地代码,而不是解释执行字节码。

以下是一个简单的循环,演示了JIT编译的潜在优化:

public class JITDemo {public static void main(String[] args) {long start = System.nanoTime();for (int i = 0; i < 1000000; i++) {calculateSum(i);}long end = System.nanoTime();System.out.println("Time taken: " + (end - start) / 1000000 + " ms");}private static int calculateSum(int n) {return n * (n + 1) / 2;}
}

在这个例子中,calculateSum方法被频繁调用,很可能会被JIT编译器识别为热点代码并进行优化。

程序执行:finally,代码开始运行

image.png

经过前面的所有阶段,Java程序终于开始真正执行。JVM会创建主线程,并调用程序的main方法作为入口点。

执行过程中,JVM会管理以下关键组件:

  1. 方法区:存储类信息、常量、静态变量等。
  2. :存储对象实例。
  3. Java栈:每个线程都有自己的Java栈,用于存储局部变量和方法调用信息。
  4. 程序计数器:记录当前线程执行的字节码指令地址。
  5. 本地方法栈:用于支持native方法的调用。

下面是一个稍微复杂一点的Java程序执行示例:

public class ExecutionDemo {private static int staticVar = 10;public static void main(String[] args) {int localVar = 5;Object obj = new Object();System.out.println("Static variable: " + staticVar);System.out.println("Local variable: " + localVar);System.out.println("Object: " + obj);calculateAndPrint(localVar);}private static void calculateAndPrint(int num) {int result = num * staticVar;System.out.println("Result: " + result);}
}

在这个例子中:

  • staticVar存储在方法区
  • obj实例存储在堆中
  • localVar和方法参数存储在Java栈中
  • maincalculateAndPrint方法的执行信息也存储在Java栈中

垃圾回收:自动内存管理

image.png

垃圾回收(GC)是Java内存管理的核心机制,它自动回收不再使用的对象,释放内存。GC的存在使得Java开发者不需要手动管理内存,大大降低了内存泄漏和悬挂指针等问题的风险。

GC的基本过程包括:

  1. 标记:识别哪些对象还在使用,哪些已经不再使用。
  2. 清除:回收不再使用的对象占用的内存。
  3. 压缩:(可选)将存活的对象移动到连续的内存空间,减少内存碎片。

Java提供了不同的GC算法和收集器,如Serial、Parallel、CMS和G1等,以适应不同的应用场景。

以下是一个简单的演示GC工作的例子:

public class GCDemo {public static void main(String[] args) {for (int i = 0; i < 100000; i++) {createObject();}System.gc();  // 建议JVM进行垃圾回收}private static void createObject() {byte[] buffer = new byte[1024];  // 创建一个1KB的对象}
}

在这个例子中,我们创建了大量的临时对象。当这些对象不再被引用时,它们就成为了垃圾,等待被GC回收。

深入案例分析:一个完整的Java程序执行过程

完整的Java程序执行过程.png

让我们通过一个更复杂的例子,来全面理解Java程序的执行过程。我们将创建一个简单的学生管理系统,并分析其执行过程。

import java.util.ArrayList;
import java.util.List;public class StudentManagementSystem {private static List<Student> students = new ArrayList<>();public static void main(String[] args) {// 添加学生addStudent(new Student("Alice", 20, "Computer Science"));addStudent(new Student("Bob", 22, "Mathematics"));addStudent(new Student("Charlie", 21, "Physics"));// 打印所有学生System.out.println("All students:");printAllStudents();// 查找学生String nameToFind = "Bob";Student foundStudent = findStudent(nameToFind);if (foundStudent != null) {System.out.println("\nFound student: " + foundStudent);} else {System.out.println("\nStudent not found: " + nameToFind);}// 删除学生String nameToRemove = "Charlie";boolean removed = removeStudent(nameToRemove);System.out.println("\nRemoving student " + nameToRemove + ": " + (removed ? "success" : "failed"));// 再次打印所有学生System.out.println("\nAll students after removal:");printAllStudents();}private static void addStudent(Student student) {students.add(student);}private static void printAllStudents() {for (Student student : students) {System.out.println(student);}}private static Student findStudent(String name) {for (Student student : students) {if (student.getName().equals(name)) {return student;}}return null;}private static boolean removeStudent(String name) {return students.removeIf(student -> student.getName().equals(name));}
}class Student {private String name;private int age;private String major;public Student(String name, int age, String major) {this.name = name;this.age = age;this.major = major;}public String getName() {return name;}@Overridepublic String toString() {return "Student{name='" + name + "', age=" + age + ", major='" + major + "'}";}
}

现在,让我们分析这个程序的执行过程:1. 编译过程

首先,我们需要编译这段代码。假设我们将其保存为StudentManagementSystem.java文件,然后使用以下命令编译:

javac StudentManagementSystem.java

这个命令会生成两个.class文件:StudentManagementSystem.classStudent.class。这些文件包含了Java字节码。

  1. 类加载

当我们运行程序时(使用java StudentManagementSystem命令),JVM启动并开始加载必要的类:

  • 首先,引导类加载器加载核心Java类,如java.lang.Objectjava.util.ArrayList等。
  • 然后,应用类加载器加载我们的StudentManagementSystemStudent类。
  1. 字节码验证

JVM会验证加载的字节码,确保其符合Java语言规范和JVM规范。例如,它会检查:

  • 方法调用是否合法
  • 字段访问是否正确
  • 类型转换是否安全
  1. 准备和解析

JVM为静态字段分配内存,如private static List<Student> students。此时,它被初始化为null。

  1. 初始化

执行类的静态初始化器。在我们的例子中,students列表被初始化为一个新的ArrayList实例。

  1. 方法调用和执行

JVM开始执行main方法:

  • 创建并添加学生对象:

    addStudent(new Student("Alice", 20, "Computer Science"));
    addStudent(new Student("Bob", 22, "Mathematics"));
    addStudent(new Student("Charlie", 21, "Physics"));
    

    每次调用new Student(...)都会在堆内存中创建一个新的Student对象。

  • 打印所有学生:

    System.out.println("All students:");
    printAllStudents();
    

    这里涉及到遍历ArrayList和调用每个Student对象的toString()方法。

  • 查找学生:

    Student foundStudent = findStudent(nameToFind);
    

    这个操作涉及到字符串比较和条件判断。

  • 删除学生:

    boolean removed = removeStudent(nameToFind);
    

    这里使用了Lambda表达式,可能会触发JIT编译。

  1. JIT编译

如果某些方法被频繁调用(例如,在一个大型系统中,findStudent方法可能会被多次调用),JIT编译器会将这些热点代码编译成本地机器码以提高性能。

  1. 垃圾回收

在程序执行过程中,JVM的垃圾回收器会定期运行,回收不再被引用的对象。例如,当我们删除"Charlie"这个学生时,如果没有其他引用指向这个Student对象,它就会被标记为可回收的垃圾。

  1. 程序终止

main方法执行完毕后,JVM会开始关闭过程:

  • 调用所有已注册的关闭钩子(shutdown hooks)
  • 执行所有未被调用的finalizer方法
  • 最后,JVM itself终止

常见问题与优化技巧

image.png

在理解了Java程序的执行过程后,我们可以更好地应对一些常见问题并进行优化:

  1. 类加载问题

    • 问题:ClassNotFoundExceptionNoClassDefFoundError
    • 解决:检查类路径(classpath)设置,确保所有必要的类都在类路径中
  2. 内存溢出

    • 问题:OutOfMemoryError
    • 解决:增加堆内存大小(-Xmx参数),检查代码中是否存在内存泄漏
  3. 性能优化

    • 使用适当的数据结构和算法
    • 避免创建不必要的对象,特别是在循环中
    • 利用Java 8+的流操作和Lambda表达式
    • 使用线程池而不是直接创建线程
  4. JIT编译优化

    • 编写简洁、直接的代码,避免过度复杂的控制流
    • 对于性能关键的代码,可以考虑使用JMH(Java Microbenchmark Harness)进行基准测试
  5. 垃圾回收调优

    • 选择合适的GC算法(如G1GC)
    • 调整堆大小和代的比例
    • 使用工具如VisualVM或JConsole监控GC活动

示例:优化学生查找操作

import java.util.HashMap;
import java.util.Map;public class OptimizedStudentManagementSystem {private static Map<String, Student> studentMap = new HashMap<>();// ... 其他方法保持不变private static void addStudent(Student student) {studentMap.put(student.getName(), student);}private static Student findStudent(String name) {return studentMap.get(name);}private static boolean removeStudent(String name) {return studentMap.remove(name) != null;}
}

这个优化版本使用HashMap来存储学生,使得查找和删除操作的时间复杂度从O(n)降低到O(1)。

总结与展望

image.png

通过深入了解Java程序的执行过程,我们可以更好地理解Java语言的工作原理,从而编写出更高效、更健壮的代码。从源代码编写到最终程序执行,每一个阶段都扮演着重要的角色:

  1. 编写源代码:奠定程序的基础
  2. 编译过程:将人类可读的代码转换为JVM可理解的字节码
  3. 类加载:将必要的类加载到JVM中
  4. 字节码验证:确保代码的安全性
  5. 即时编译:提高热点代码的执行效率
  6. 程序执行:真正运行程序逻辑
  7. 垃圾回收:自动管理内存,简化开发

随着Java语言的不断发展,我们可以期待看到更多的优化和改进:

  • 更智能的JIT编译:利用机器学习技术来预测和优化热点代码
  • 改进的垃圾回收算法:进一步减少GC暂停时间,提高并发性能
  • 模块化系统的完善:Java 9引入的模块系统将得到更广泛的应用,有助于构建更加模块化和可维护的大型应用
  • 更好的多核CPU支持:随着多核处理器的普及,Java在并发编程方面将有更多的优化
  • 与新兴技术的集成:如更好的云原生支持、大数据处理能力的增强等

作为开发者,我们应该持续学习和跟进Java的新特性和最佳实践,同时深入理解Java程序的执行过程,这将有助于我们编写出更高质量的代码,构建更高效的系统。

Java的魅力在于它既简单易学,又蕴含着强大的内在机制。通过本文的讲解,希望你能对Java程序的执行过程有了更深入的理解。记住,理解原理是提高编程技能的关键。继续探索,不断实践,你将成为一个更优秀的Java开发者!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/395536.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot企业人事管理系统-附源码与配套论文

1.1引言 随着计算机技术的飞速发展&#xff0c;计算机在各种单位机构管理中应用的普及﹐管理信息系统的开发在强调管理、强调信息的现代社会中也显得越来越重要。因此,利用计算机高效率地完成人事管理的日常事务&#xff0c;是适应现代各种单位机构制度要求、推动各种单位机构…

SpringBoot统一功能处理——拦截器

目录 一、什么是拦截器&#xff1f; 二、拦截器使用 2.1 定义拦截器 2.2 注册配置拦截器 三、拦截器详解 3.1 拦截器的拦截路径配置 3.2 拦截器执行流程 一、什么是拦截器&#xff1f; 拦截器是Spring框架提供的核心功能之一, 主要用来拦截用户的请求, 在指定方法前后,…

【2024年精选】分享7款国内大学ai写论文推荐网站工具

在2024年&#xff0c;AI技术的飞速发展为学术研究和论文写作带来了革命性的变化。众多AI论文写作工具应运而生&#xff0c;帮助学生和研究人员提高写作效率&#xff0c;提升论文质量。其中&#xff0c;AIPaperPass作为一款备受瞩目的国内AI写论文推荐网站工具&#xff0c;以其独…

CentOS7.6 HAproxy-7层负载均衡集群——实施方案

目录 1、前期环境准备 1.准备4台主机 1. 设置主机名 2. 设置IP地址然后重启网卡 3. 关闭防火墙和selinux 4. 全部的服务器完成时间统一 二、配置haproxy(192.168.200.11)服务器 1. 安装haproxy 2. haproxy 配置中分成五部分内容 3. 配置HAproxy&#xff08;192.168.2…

Animate软件基本概念:缓动、绘图纸外观及图层

FlashASer&#xff1a;AdobeAnimate2021软件零基础入门教程https://zhuanlan.zhihu.com/p/633230084 FlashASer&#xff1a;实用的各种Adobe Animate软件教程https://zhuanlan.zhihu.com/p/675680471 FlashASer&#xff1a;Animate教程及作品源文件https://zhuanlan.zhihu.co…

05_ Electron 自定义菜单、主进程与渲染进程通信

Electron 自定义菜单、主进程与渲染进程通信 一、定义顶部菜单二、Electron 自定义右键菜单1、使用 electron/remote 模块实现 三、 Electron 主进程和渲染进程通信场景1&#xff1a;渲染进程给主进程发送异步消息场景2&#xff1a;渲染进程给主进程发送异步消息&#xff0c;主…

数据结构--单链

#include "link.h" plink get_head() { plink pmalloc(sizeof(Link)); if(pNULL) { printf("申情节点失败\n"); return NULL; } p->len0; p->nextNULL; return p; } void head_insert(plink L,int a) {…

推荐一个uniapp选择文件上传的插件

插件地址&#xff1a;文件选择、文件上传组件&#xff08;图片&#xff0c;视频&#xff0c;文件等&#xff09; - DCloud 插件市场 支持 H5 / App / 微信小程序

K8s问题案例分析

1.worker节点宕机&#xff0c;请说明一下pod的驱逐流程&#xff1a; k8s有一个节点控制器&#xff0c;节点控制器在一段时间内无法和kubelet通信&#xff0c;那么就会给节点打上unknown 状态&#xff0c;并自动创建NoExecute污点,避免调度器调度新的pod到该节点。同时已经在这…

基于目标检测的目标跟踪(python)

文章目录 概要环境准备目标检测实现目标跟踪实现整合后的代码可能遇到的问题Could not load library libcudnn_ops_infer.so.8. Error: libcudnn_ops_infer.so.8: cannot open shared object file: No such file or directory参考概要 基于目标检测的目标跟踪过程通常包括以下…

Python新手错误集锦(PyCharm)

# 自学Python&#xff0c;用Pycharm作环境。我这个手新到这时我学习的第一个编程软件&#xff0c;且本人专业是化学&#xff0c;以前对电脑最高级的使用是玩扫雷游戏。所以这里集合的错误都是小透明错误&#xff0c;大部分人请绕道。不断更新中...... 缩进错误 记住“indent”…

力扣面试经典算法150题:买卖股票的最佳时机

买卖股票的最佳时机 今天的题目是力扣面试经典150题中的数组的简单题: 多数元素 题目链接&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/?envTypestudy-plan-v2&envIdtop-interview-150 题目描述 给定一个数组 prices&#xf…

SX_错误声明定义了两个以上的数据类型BUG解决_14

具体报错&#xff1a; In file included from perfmon_priv.h:32,from perfmond.c:21: perfmon_api.h:7:18: 错误: 声明指定了两个以上的数据类型7 | #define uint8_t unsigned char perfmon_api.h:7:27: 错误: 声明指定了两个以上的数据类型7 | #define uint8_t unsigned cha…

大数据Flink(一百零六):什么是阿里云实时计算Flink版

文章目录 什么是阿里云实时计算Flink版 一、产品概述 二、产品架构 三、产品优势 什么是阿里云实时计算Flink版 阿里云实时计算Flink版是一套基于Apache Flink构建的⼀站式实时大数据分析平台&#xff0c;提供端到端亚秒级实时数据分析能力&#xff0c;并通过标准SQL降低业…

c++ - c++11(1)

文章目录 前言一、统一的列表初始化1、使用{ }初始化2、 std::initializer_list 二、声明1、auto2、decltype3、nullptr 三、范围for循环四、右值引用1、左值引用和右值引用2、左值引用和右值引用的比较3、左值引用的使用场景4、右值引用的使用场景5、完美转发 前言 一、统一的…

Python爬虫入门实战(详细步骤)

1. 技术选型 爬虫这个功能&#xff0c;我个人理解是什么语言都能写的&#xff0c;只要能正常发送 HTTP 请求&#xff0c;将响应回来的静态页面模版 HTML 上把我们所需要的数据提取出来就可以了&#xff0c;原理很简单&#xff0c;这个东西当然可以手动去统计收集&#xff0c;但…

【C语言】预处理详解(上)

文章目录 前言1. 预定义符号2. #define 定义常量3. #define定义宏4. 带有副作用的宏参数5. 宏替换的规则 前言 在讲解编译和链接的知识点中&#xff0c;我提到过翻译环境中主要由编译和链接两大部分所组成。 其中&#xff0c;编译又包括了预处理、编译和汇编。当时&#xff0c…

【准则化的思想】变异测试的真正价值

下面我们来讨论变异充分准则。这个准则&#xff0c;同样是一种基于缺陷的充分准则&#xff0c;但是跟我们前面讨论过的准则相比&#xff0c;思路又完全不同。我们来具体看一看。 首先&#xff0c;它为什么叫“变异”充分准则呢&#xff1f;我们通常说的变异&#xff0c;指的是…

【0304】psql 执行“VACUUM FULL”命令的背后实现过程

1. 概述 在前面讲解Postgres内核中解析器相关(【0297】Postgres内核之 INSERT INTO 原始解析树 转 Query 树 (1))内容时,曾提到过,Postgres内核大致将用户下发的SQL语句分为三大类,这里的VACUUM FULL属于CMD_UTILITY; 因此直接调用utility.c(实用程序)中的对应函数。…

SQL Server Management Studio的使用

之前在 https://blog.csdn.net/fengbingchun/article/details/140961550 介绍了在Windows10上安装SQL Server 2022 Express和SSMS&#xff0c;这里整理下SSMS的简单使用&#xff1a; SQL Server Management Studio(SSMS)是一种集成环境&#xff0c;提供用于配置、监视和管理SQL…