一个java文件的JVM之旅

准备

我是小C同学编写得一个java文件,如何实现我的功能呢?需要去JVM(Java Virtual Machine)这个地方旅行。

变身

我高高兴兴的来到JVM,想要开始JVM之旅,它确说:“现在的我还不能进去,需要做一次转换,生成class文件才行”。为什么这样呢?

JVM不能直接加载java文件的原因:

  • Java源代码中包含了许多高级语言特性和语法,比如类、继承、多态、异常处理等等。这些高级特性在JVM中没有直接对应的形式,只有通过编译器的处理才能转化为JVM可以理解的字节码指令。

  • Java源代码需要经过编译器的编译过程,才能生成相应的字节码文件,然后再由JVM加载、解释执行。在编译过程中,编译器对源代码进行语法分析、类型检查、优化等操作,最终生成与目标平台兼容的Java字节码文件。

  • JVM只能够加载和运行符合Java虚拟机规范的.class字节码文件,而不能够直接加载和运行Java源代码文件。

编译

知道原因后,我又问JVM,我怎么才能变成class文件呢,JVM告诉我可以通过javac命令。

javac

javac 是 Java 编译器命令,用于将 Java 源代码文件编译成字节码文件(.class 文件)。

命令格式

javac [options] [source files]

  • options:为编译选项,可以控制编译器的行为,例如指定类路径、生成调试信息、压缩文件等。

  • source files:为需要编译的 Java 源代码文件,可以指定多个文件,用空格隔开。如果不指定源代码文件,则 `javac` 命令会在当前目录查找所有扩展名为 .java 的文件进行编译。

需要注意的是,`javac` 命令需要在正确配置 JDK 环境后才能使用。JDK(Java Development Kit)是 Java 开发工具包的缩写,是 Java 应用程序开发的核心组件之一。

具体实现

编译器在编译源文件时,需要对源文件进行语法分析、语义分析和类型检查等操作。

  • 语法分析:javac命令首先将源文件读入内存,然后进行词法分析和语法分析。词法分析器负责将源文件中的字符序列转换成一个个单词(Token),然后语法分析器将单词组合成可以被解释执行的语法结构,形成抽象语法树(AST)。

  • 语义分析:javac命令在生成AST之后,进行语义分析。语义分析器主要是为了检查程序中是否存在语义错误,例如变量未定义、类型不匹配等,如果发现语义错误,编译器会输出错误信息,并中止编译过程,不会生成字节码文件。

  • 类型检查:javac命令在语义分析的基础上,进行类型检查。类型检查器主要是检查程序的类型是否匹配和兼容,如果类型不匹配或不兼容,编译器会在编译期间报告错误。

  • 代码生成:javac命令在生成抽象语法树后,对其进行优化和转化,最终生成字节码文件。编译器会根据目标代码的平台和版本,生成适当的字节码文件。

执行

知道怎么变身后,我立即通过javac命令,让自己变成可以被JVM执行的class文件。

加载

变成class文件后,我怎么能进入JVM内部呢,是走着去还是坐车去呢?JVM告诉我要通过类加载器进入。

类加载器

Java类加载器是Java虚拟机(JVM)中的一个重要组件,它负责将类文件(.class文件)加载到JVM中。

分类

Java 中的类加载器是按照其加载类的特点进行分类的,主要有以下几种类型:

  • 启动类加载器(Bootstrap ClassLoader):负责加载 JRE/lib/rt.jar 中的核心 Java 类库,是最顶层的类加载器,不是 Java 类(因为在 JVM 实现时就已经存在)。

  • 扩展类加载器(Extension ClassLoader):负责加载 JRE/lib/ext 目录下的扩展类库,也是由 C++ 实现的类加载器。

  • 应用程序类加载器(APP ClassLoader):负责加载应用程序的类,包括在 CLASSPATH 中指定的类库或者目录中的 Java 类。

  • 自定义类加载器(Custom ClassLoader):继承自 ClassLoader 类,实现自己的类加载器,主要用于加载一些自定义的类或者修改某些类的字节码。

查看使用的类加载器

代码:

public class ClassLoaderTest {
    public static void main(String[] args) {
        //启动类加载器
        System.out.println(String.class.getClassLoader());
        //扩展类加载器
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
        //应用程序类加载器
        System.out.println(ClassLoaderTest.class.getClassLoader());
        //扩展类加载器的父加载器
        System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getParent());
        //应用程序类加载器的父加载器
        System.out.println(ClassLoaderTest.class.getClassLoader().getParent());
    }
}

执行结果:

自定义类加载器

自定义类加载器主要包括两种类型:

  • 独立的自定义类加载器,通过重载 ClassLoader 类中的 findClass 方法来实现加载类文件的功能;

  • 基于 URLClassLoader 类实现的自定义类加载器,使用 URL 的形式来指定类文件的位置。

重载ClassLoader

代码:

public class CustomClassLoader extends ClassLoader {
    private String basePath;

    public CustomClassLoader(String basePath) {
        this.basePath = basePath;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = getClassData(name);
        if (data == null) {
            throw new ClassNotFoundException();
        } else {
            // 使用 defineClass 方法将 byte 数组转换为 Class 对象
            return defineClass(name, data, 0, data.length);
        }
    }

    private byte[] getClassData(String className) {
        String path = basePath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
        try (InputStream inputStream = new FileInputStream(path);
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, length);
            }
            return outputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

说明:

上述代码继承了ClassLoader类,并重写了其中的findClass()方法,实现从指定目录中加载类文件的功能。

findClass()方法中,首先通过getClassData()方法读取并返回类文件的字节数组,如果获取的字节数组为空,则抛出ClassNotFoundException异常;否则,使用defineClass()方法将字节数组转换为 Class 对象,并返回该对象。

getClassData()方法中,根据传入的类名生成类文件路径,并使用FileInputStream将类文件读入字节数组中。

使用:

public class CustomClassLoaderTest {
    public static void main(String[] args) throws Exception {
        // 创建自定义类加载器,指定类文件所在的目录
        CustomClassLoader classLoader = new CustomClassLoader("F:\\classes");

        // 使用自定义类加载器加载 Hello 类
        Class<?> clazz = classLoader.loadClass("com.example.something.Hello");
        Object obj = clazz.getDeclaredConstructor().newInstance();
        System.out.println(obj);
    }
}

基于 URLClassLoader

代码:

public class CustomURLClassLoader extends URLClassLoader {
    public CustomURLClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 调用父类 loadClass 方法进行委托加载
            Class<?> clazz = super.findClass(name);
            return clazz;
        } catch (ClassNotFoundException e) {
            // 如果父类无法加载,则尝试在 URL 中加载
            byte[] data = getClassData(name);
            if (data == null) {
                throw new ClassNotFoundException();
            } else {
                // 使用 defineClass 方法将 byte 数组转换为 Class 对象
                return defineClass(name, data, 0, data.length);
            }
        }
    }

    private byte[] getClassData(String className) {
        String path = className.replace('.', '/') + ".class";
        URL[] urls = getURLs();
        for (URL url : urls) {
            try {
                URL classUrl = new URL(url, path);
                // 使用 URLConnection 检查类文件是否存在
                try (InputStream is = classUrl.openStream();
                     ByteArrayOutputStream os = new ByteArrayOutputStream()) {
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = is.read(buffer)) != -1) {
                        os.write(buffer, 0, length);
                    }
                    return os.toByteArray();
                }
            } catch (IOException e) {
                // ignore and try next URL
            }
        }
        return null;
    }
}

说明:

上述代码继承了 `URLClassLoader` 类,并重写了其中的 `findClass()` 方法,实现先尝试使用父类加载器进行加载,如果无法加载,则尝试使用 URL 加载类文件的功能。在 `getClassData()` 方法中,会遍历 `URLClassLoader` 中定义的 URL,检查类文件是否存在,并返回类文件的字节数组,如果无法找到类文件,则返回 `null`。

使用:

public class CustomURLClassLoaderTest {
    public static void main(String[] args) throws Exception {
        // 创建 URL 数组,指定类文件所在的 URL
        URL[] urls = { new URL("file:F:\\classes") };

        // 创建父类加载器,使用系统类加载器
        ClassLoader parent = ClassLoader.getSystemClassLoader();

        // 创建自定义 URL 类加载器
        CustomURLClassLoader classLoader = new CustomURLClassLoader(urls, parent);

        // 使用自定义 URL 类加载器加载 Hello 类
        Class<?> clazz = classLoader.loadClass("com.example.something.Hello");
        Object obj = clazz.getDeclaredConstructor().newInstance();
        System.out.println(obj);
    }
}

双亲委派

加载器那么多,我具体是哪个类进行加载得呢?双亲委派机制告诉我答案.

定义

双亲委派是一种Java类加载器的工作机制,它将类加载请求委派给父类加载器,直到顶级系统类加载器。基本思想是,除非有特殊需求,否则所有类的加载任务都应该由父类加载器完成,从而保证Java核心库的类型安全和稳定性,并防止恶意代码的自行布置。如果一个类没有在父类加载器中被发现,子类加载器才会尝试加载该类。这种类加载器之间的父子关系被称为“双亲委派模型”.

如图:

意义

为什么通过双亲委派进行加载呢?

  • 避免重复加载

  • 提高安全性

  • 维护Java平台的一致性

  • 代码优化

Linking

加载过后,我是否就可以被使用了呢?答案是否定的,我还要经历Lingking 阶段,包括Verification、Preparation 和 Resolution。

Verification(验证)

在验证阶段,Java虚拟机会进行语法与语义的检查,以保证class文件的完整性和正确性,同时保证被加载的class与虚拟机的版本兼容。主要的检查内容包括文件格式、字节码语义、符号引用等。

Preparation(准备)

在准备阶段,Java虚拟机会为类变量分配内存,并且赋予初始值。如果类变量包含有静态变量,那么这时也会初始化静态变量。因此,在这个阶段,类变量所使用的空间已经被分配,将其设置为默认初始值即可。

Resolution(解析)

在解析阶段,将类或接口中的符号引用转化为直接引用的过程。在 Java 虚拟机加载类时,符号引用是一种指向常量池中某个符号的引用,而直接引用则是指向内存中某个位置的直接指针。解析阶段可以理解为是在解决类之间的依赖关系,使各个类之间可以像使用自身成员一样使用别的类中的成员。

初始化

在验证、准备和解析后,我还要经过初始化,才能被使用。

定义

初始化是指在类加载过程的最后一步,JVM要对类进行一些初始化的操作,确保类可以安全地使用。在这个阶段,往往包括静态变量显式赋值和静态代码块执行。

内容

静态变量显式赋值

当类加载器完成类的加载、验证、准备后,在初始化阶段,JVM对类的静态变量进行显式赋值。如果类定义了多个静态变量,JVM会按照代码中声明的顺序进行初始化,并且若发现此过程需要访问到其他未初始化的类,JVM会先完成这些类的初始化。

静态代码块的执行

除了静态变量的显式赋值,类的静态代码块也会在初始化阶段执行。当JVM执行类加载的Initializing阶段时,会执行类中所有静态代码块的内容,如果类中没有定义静态代码块,则不执行。这个过程一般用于在使用之前对类进行初始化。

接口初始化

当一个类在初始化时,如果发现其父类还未进行初始化,JVM会先对其父类进行初始化。如果该类实现了接口,也会对这个接口进行初始化操作,接口的初始化过程和类一样,都会进行静态变量显式赋值及静态代码块执行,同时还会检查接口中的所有静态方法。

功能实现

初始化之后,我才真正的进入JVM中,其它小伙伴需要我的时候,只需要创建我的实例,就可以使用我的功能了,得到我帮助得小伙伴都很感谢我。

GC

在JVM中我过得很开心,也留下了很多足迹。在我走后,如何让我得足迹不对其他小伙伴有影响呢?GC可以帮我解决这个问题。

定义

GC(Garbage Collection)是JVM提供的垃圾回收机制。在Java中,对象是动态分配的,内存是由JVM自动管理,而不是由程序员手动分配和释放。当一个对象不再被程序引用时,就应该由垃圾回收器回收其占用的内存,这样可以防止内存泄漏和提高内存的。

小结

通过我的旅行,你知道JVM是怎么加载一个类的了么?我们通过加载、Linking、初始化和使用等各个阶段,将Java类完整地载入内存并执行其中定义的方法和变量。这个过程中,每个阶段都扮演着不同的角色,并为类的正常运行提供了必要的支持。

文章转载自:京东云技术团队

原文链接:https://www.cnblogs.com/jingdongkeji/p/17814733.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode算法题解(回溯、难点)|LeetCode332. 重新安排行程

LeetCode332. 重新安排行程 题目链接&#xff1a;332. 重新安排行程 题目描述&#xff1a; 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08…

uboot启动linux kernel的流程

目录 前言流程图autoboot_commandrun_command_listdo_bootmdo_bootm_statesdo_bootm_linuxboot_prep_linuxboot_jump_linux 前言 本文在u-boot启动流程分析这篇文章的基础上&#xff0c;简要梳理uboot启动linux kernel的流程。 流程图 其中&#xff0c; autoboot_command位于…

Error creating bean with name ‘apiModelSpecificationReader‘ defined in URL

问题&#xff1a; 启动项目的时候&#xff0c;报错了 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name apiModelSpecificationReader defined in URL [jar:file:/D:/.gradle/caches/modules-2/files-2.1/io.springfox/sp…

【亚马逊云科技产品测评】活动征文|10分钟拥有一台AWS Linux系统

前言 在数字化时代&#xff0c;AWS云服务扮演着至关重要的角色。AWS&#xff08;Amazon Web Services&#xff09;是亚马逊公司旗下的云计算服务平台&#xff0c;为全球各地的企业、组织和个人开发者提供了一系列广泛而深入的云服务。 在AWS云服务中&#xff0c;计算、存储、数…

selenium css定位

selenium-css定位 element_css driver.find_element(By.CSS_SELECTOR, css表达式)css定位说明 selenium中的css定位&#xff0c;实际是通过css选择器来定位到具体元素&#xff0c;css选择器来自于css语法 css定位优点 语法简洁对比其他定位方式&#xff0c;定位效率更快对…

「Java开发指南」如何用MyEclipse搭建Spring MVC应用程序?(二)

本教程将指导开发者如何生成一个可运行的Spring MVC客户应用程序&#xff0c;该应用程序实现域模型的CRUD应用程序模式。在本教程中&#xff0c;您将学习如何&#xff1a; 从数据库表的Scaffold到现有项目部署搭建的应用程序 使用Spring MVC搭建需要MyEclipse Spring或Bling授…

发电机负载测试:专业指南

发电机负载测试是一项关键的测试过程&#xff0c;发电机负载测试的专业指南&#xff0c;帮助您进行有效的测试。 测试前准备&#xff1a;确保发电机和测试设备处于良好的工作状态&#xff0c;检查发电机的电源和燃料供应是否正常&#xff0c;确保测试设备和发电机之间的连接正确…

Unity Mirror学习(二) Command特性使用

Command&#xff08;命令&#xff09;特性 1&#xff0c;修饰方法的&#xff0c;当在客户端调用此方法&#xff0c;它将在服务端运行&#xff08;我的理解&#xff1a;客户端命令服务端做某事&#xff1b;或者说&#xff1a;客户端向服务端发消息&#xff0c;消息方法&#xff…

高能数造电池3D打印智能制造小试线,开启全固态电池数字化新时代

在科技创新的浪潮中&#xff0c;电池制造领域又迎来了一次突破性的进展。近日&#xff0c;高能数造(西安)技术有限公司重磅推出了其最新电池数字制造装备——全固态电池3D打印智能制造小试线 &#xff0c;这一创新性的技术开启了全固态电池的数字化智造新时代&#xff0c;为全固…

【ElasticSearch系列-06】Es集群架构的搭建以及集群的核心概念

ElasticSearch系列整体栏目 内容链接地址【一】ElasticSearch下载和安装https://zhenghuisheng.blog.csdn.net/article/details/129260827【二】ElasticSearch概念和基本操作https://blog.csdn.net/zhenghuishengq/article/details/134121631【三】ElasticSearch的高级查询Quer…

如何提高小红书笔记的互动率

相信有很多新手在运营小红书的时候&#xff0c;可能都会遇到过以下这样的情况&#xff1a; 笔记点赞、收藏数据明明还可以&#xff0c;但评论区却没有人留言&#xff1f;为何大家只给点赞、收藏&#xff0c;却不关注账号&#xff1f; 其实&#xff0c;这背后有很多运营技巧&a…

怎么调整excel表里面所有单元格中,某个相同字体大小,单元格中其他文字大小不变?

环境: excel 2021 python3.8 问题描述: 怎么调整excel表里面所有单元格里面1这个字体大小,单元格里面其他文字不变? excel表里面。很多单元格都有1,1和文字都是10号字体,现在想把全部1字字体调整为16号其他字大小都不变 解决方案: 一、使用python来实现,经过测…

caffe搭建squeezenet网络的整套工程

之前用pytorch构建了squeezenet&#xff0c;个人觉得pytorch是最好用的&#xff0c;但是有的工程就是需要caffe结构的&#xff0c;所以本篇也用caffe构建一个squeezenet网络。 数据处理 首先要对数据进行处理&#xff0c;跟pytorch不同&#xff0c;pytorch读取数据只需要给数据…

Java 设计模式——解释器模式

目录 1.概述2.结构3.案例实现3.1.抽象表达式类3.2.终结表达式3.3.非终结表达式3.4.环境类3.5.测试 4.优缺点5.使用场景 1.概述 &#xff08;1&#xff09;如下图&#xff0c;设计一个软件用来进行加减计算。我们第一想法可能就是使用工具类&#xff0c;提供对应的加法和减法的…

2023最新版JavaSE教程——第5天:数组

目录 一、数组的概述1.1 为什么需要数组1.2 数组的概念1.3 数组的分类 二、一维数组的使用2.1 一维数组的声明2.2 一维数组的初始化2.2.1 静态初始化2.2.2 动态初始化 2.3 一维数组的使用2.3.1 数组的长度2.3.2 数组元素的引用 2.4 一维数组的遍历2.5 数组元素的默认值 三、一维…

03【远程协作开发、TortoiseGit、IDEA绑定Git插件的使用】

上一篇&#xff1a;02【Git分支的使用、Git回退、还原】 下一篇&#xff1a;【已完结】 目录&#xff1a;【Git系列教程-目录大纲】 文章目录 一、远程协作开发1.1 远程仓库简介1.1.1 Github1.1.2 Gitee1.1.3 其他托管平台 1.2 发布远程仓库1.2.1 创建项目1&#xff09; 新…

解决:AttributeError: ‘WebDriver‘ object has no attribute ‘find_element_by_xpath‘

解决&#xff1a;AttributeError: ‘WebDriver’ object has no attribute ‘find_element_by_xpath’ 背景 在使用之前的代码通过selenium定位元素时&#xff0c;报错&#xff1a;selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to l…

Altium Designer学习笔记1

一、新建项目和文件&#xff1a; 1、新建Project项目&#xff1b; 2、新建原理图文件&#xff1b; 3、新建PCB项目&#xff1b; 在工程文件上点击右键&#xff0c;保存为&#xff0c;可以依次保存三个文件。选择需要保存的路径&#xff0c;新建文件夹。 依次是原理图文件、…

如何用Java高效地存入一万条数据?这可能是你面试成功的关键!

大家好&#xff0c;我是你们的小米&#xff0c;一个热爱技术、喜欢分享的29岁程序猿。今天我要和大家聊一聊一个常见的面试题&#xff1a;在Java中&#xff0c;当我们需要将一万条数据存储到数据库时&#xff0c;如何能够提高存储效率呢&#xff1f; 在面试过程中&#xff0c;…

事务(本地事务与分布式事务)

事务 1 本地事务1.1 事务的特性1.2 事务的隔离级别1.3 事务的传播属性 2 分布式事务2.1 分布式事务基础2.1.1 CAP定理2.1.2 BASE定理 2.2 分布式事务的解决方案2.2.1 两阶段提交&#xff08;2PC&#xff09;2.2.2 TCC补偿式事务2.2.3 消息事务最终一致性 1 本地事务 1.1 事务的…