• JVM基础:字节码文件详解①


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、Java虚拟机的组成

    👉它可以分为以下四个部分

    1. 类加载器(ClassLoder)加载class字节码文件中的内容到内存中(加载到内存是为了高效的利用)
    2. 运行时数据区域(JVM管理的内存)负责管理JVM使用到的内存,比如创建对象和销毁对象
    3. 执行引擎((即时编译器、解释器与垃圾回收器等)将字节码文件中的指令解
      释成机器码,同时使用即时编译器优化性能
    4. 本地接口调用本地已经编译的方法,比如虚拟机中提供的c/c++的方法,就是本地方法(即jvm底层已经实现好的,用C/C++语言编写好的方法,使用native修饰的方法)在这里插入图片描述

    👉基本执行流程如下所示:

    在这里插入图片描述


    二、字节码文件的组成

    2.1 为什么要了解字节码文件?

    👉原因

    ①它可以解决一些面试难题

    例如以下面试题

    • int i = 0; i = i++; 最终i的值是多少?
    • 请你回答一下Java的反射是如何实现的?

    ②它可以解决工作中的一些实际问题——版本冲突

    例如以下报错

    在这里插入图片描述

    2.2 如何“窥探”字节码文件的奥秘?

    2.2.1 使用工具打开字节码文件

    👉常用工具

    ①使用Jclasslib字节码插件【idea插件】

    在这里插入图片描述

    如何使用该插件查看指定字节码文件?

    👉步骤

    ①在idea中 file – settings – plugins 中搜索 jclass 安装该插件

    在这里插入图片描述
    ②选中指定的Java源文件,按照以下步骤操作,即可
    在这里插入图片描述
    在这里插入图片描述

    👉注意

    使用jclasslib的idea插件版的两个小细节

    1. 要打开一个新的字节码文件,就需要选中当前的源代码,然后点击 view - show bytecode with jclasslib 就可以打开新的字节码文件

      在这里插入图片描述

      1. 如果源代码发生变化,需要重新运行编译,可通过 build - Recompile ‘xxx.java’ 或 重新运行该源代码的方式重新编译,然后在jclasslib中重新刷新即可
        在这里插入图片描述

    ②使用Javap命令

    如何使用该命令查看指定字节码文件?

    👉步骤

    1. 首先确保已经安装了JDK(Java Development Kit),因为Javap是JDK的一部分。

    2. 打开命令提示符(Windows)或终端(macOS/Linux)。

    3. 使用cd命令导航到包含字节码文件(.class文件)的目录。例如,如果字节码文件位于C:\Users\YourUsername\Documents\MyProject目录下,请输入cd C:\Users\YourUsername\Documents\MyProject

    4. 输入javap -c YourClassName.class命令,其中YourClassName是你要查看的字节码文件的名称(不包括扩展名)。例如,如果你要查看名为MyClass.class的文件,请输入javap -c MyClass.class

    5. 按回车键执行命令。命令将显示字节码文件的详细信息,包括类名、方法名、参数类型等。

    案例:使用Javap命令打开桌面测试文件夹中的字节码文件t1.class

    在这里插入图片描述

    👉备注

    如果jar包需要先使用 jar –xvf 命令解压

    ③使用Arthas工具打开字节码文件

    GitHub地址

    在这里插入图片描述

    2.2.2 字节码文件是由哪几部分组成?

    使用jclass插件打开任意一个Java 源文件,我们可以看到如下信息

    在这里插入图片描述

    • 一般信息(基本信息)魔数、字节码文件对应的Java版本号,访问标识(public final等等)以及父类和接口

      在这里插入图片描述

    • 常量池保存了字符串常量、类或接口名、字段名,主要在字节码指令中使用

      在这里插入图片描述

    • 接口当前类实现的接口信息

      在这里插入图片描述

    • 字段当前类或接口声明的字段信息

      在这里插入图片描述

    • 方法当前类或接口声明的方法信息——字节码指令

      在这里插入图片描述

    • 属性类的属性,比如源码的文件名,内部类的列表等

      在这里插入图片描述

    2.2.3 基础信息(一般信息)

    在这里插入图片描述

    前面提到的基本信息中主要包含魔数、字节码文件对应的Java版本号,访问标识(public final等等)以及父类和接口等内容,但是有两个问题值得深思

    在这里插入图片描述

    😥问题①:何为魔数?

    使用notepad++ 随便打开两个字节码文件,我们可以看到他们之间显著的共通之处

    a.打开t1.class字节码文件,如下图所示

    在这里插入图片描述

    b.打开MyApplication.class字节码,如下图所示

    在这里插入图片描述

    共通之处

    以ca fe ba be打头

    这就是魔数(Magic),用以校验文件类型的文件头,如果别的编译软件不支持该种类型的文件头,则解析文件时会出错。那为什么不使用文件扩展名去校验类型?文件是无法通过文件扩展名来确定文件类型的,文件扩展名可以随意修改,不影响文件的内容。故而在Java字节码文件中,将文件头称为魔数

    在这里插入图片描述

    😥问题②:何为Java版本号?

    Java版本号主要分为主副版本号,主副版本号指的是编译字节码文件的JDK版本号,主版本号用来标识大版本号,JDK1.0-1.1使用了45.0-45.3,JDK1.2是46之后每升级一个大版本就加1;副版本号是当主版本号相同时作为区分不同版本的标识,一般只需要关心主版本号。

    在这里插入图片描述

    👉版本号作用

    主要判断当前字节码的版本和运行时的JDK是否兼容

    👉备注

    1.2之后大版本号计算方法就是:主版本号 – 44 ;
    比如主版本号52,那就是JDK8

    之前在前文中抛出了一个版本冲突的问题

    如下图所示

    在这里插入图片描述

    👉原因

    主版本号不兼容,发生冲突

    👉解决方案

    1.升级JDK版本(容易引发其他的兼容性问题,并且需要大量的测试)
    2.将第三方依赖的版本号降低或者更换依赖,以满足JDK版本的要求 √ 建议采用

    👉总结

    在这里插入图片描述

    2.2.4 常量池

    👉作用

    避免相同的内容重复定义,节省空间

    👉概述

    • 常量池中的数据都有一个编号,编号从1开始。在字段或者字节码指令中通过编号可以快速的找到对应的数据
    • 字节码指令中通过编号引用到常量池的过程称之为符号引用

    👉符号引用的示意图如下所示

    在这里插入图片描述

    2.2.5 方法

    👉先看一个简单经典的面试题

    int i = 0; i = i++; 最终i的值是多少?

    👉我的回答

    i =i++ 是先赋值后自增,而i= ++ i 是先自增后赋值,所以最终的i是0

    😚进一步发问

    为什么i =i++ 是先赋值后自增,而i= ++ i 是先自增后赋值,如果根据Java的运算符优先级对比,应该是1吧,i++优先级高,先执行之后将返回结果1赋值给 i,所以最终 i应该是1。

    可正确的答案是 i最终的值是0

    😥why?

    莫急,且听我慢慢道来

    👉定义

    字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中

    在这里插入图片描述

    选中Code属性,我们可以看到它下面还有两个Table

    在这里插入图片描述

    • LineNumberTable用于存储源代码中各行号与字节码指令之间的对应关系,它可以帮助调试器在执行程序时定位到源代码中的具体位置。

      在这里插入图片描述

    • LocalVariableTable主要用于存储方法的参数和方法内定义的局部变量。在程序编译为Class文件时,会在Code属性的max_locals数据项中确定该方法所需要分配的局部变量表的最大容量。

      在这里插入图片描述

    我们暂时只关心LocalVariableTable,如下图所示,LocalVariableTable表中的每个项都包含以下内容:

    • 名称(Name)表示该项对应的局部变量的名称
    • 描述符(Descriptor)表示该项对应的局部变量的类型和修饰符
    • 索引(Slot)表示该项在局部变量表中的位置
    • 值(Value)表示该项对应的局部变量的值

    在这里插入图片描述

    除了上面的LocalVariableTable,我们还得了解一个概念——操作数栈

    😥什么是操作数栈?

    👉讯飞星火告诉我们

    在这里插入图片描述

    简单来讲,操作数栈是临时存放数据的地方

    👉再看之前的源代码

     int i=0;
     int j= i+1;
    
    • 1
    • 2

    字节码指令分析如下

    在这里插入图片描述
    回到前面提到的面试题

    😥为什么int i = 0; i = i++; 最终i的值是0?

    源代码示例如下

    public static void main(String[] args) {
            int i=0;
            i=i++;
            System.out.println("i = " + i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    👉分析流程如下所示

    在这里插入图片描述
    👉举例分析

    以 int i=0; i=++i; 的字节码指令展开分析,最后i的值是多少?

    示例代码如下

     public static void main(String[] args) {
            int i=0;
            i=++i;
            System.out.println("i = " + i);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    字节码指令如下

     0 iconst_0
     1 istore_1
     2 iinc 1 by 1
     5 iload_1
     6 istore_1
     7 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
    10 new #3 <java/lang/StringBuilder>
    13 dup
    14 invokespecial #4 <java/lang/StringBuilder.<init> : ()V>
    17 ldc #5 <i = >
    19 invokevirtual #6 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
    22 iload_1
    23 invokevirtual #7 <java/lang/StringBuilder.append : (I)Ljava/lang/StringBuilder;>
    26 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
    29 invokevirtual #9 <java/io/PrintStream.println : (Ljava/lang/String;)V>
    32 return
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    👉流程分析如下

    ①将int类型的 o push 操作数栈中;
    ②从操作数栈中取出0,放入到局部变量表中位序为1的位置上[i],此时i=0;
    ③在局部变量表中为位序为1的位置上增加1,此时i=1;
    ④从局部变量表中位序为1的位置将数据压入到操作数栈中,此时i=0;
    ⑤将操作数栈中的数据[1]保存到局部变量表中位序为1的位置上,此时i=1;

    👉备注

    如果不清楚某一条指令的作用,可采取以下步骤

    ①选中指令,点击“显示JVM规范”

    在这里插入图片描述
    ②浏览器会自动跳转至对应指令的详情页面

    在这里插入图片描述


    参考目录

    https://www.bilibili.com/video/BV1r94y1b7eS?p=7&spm_id_from=pageDriver&vd_source=5a34715e416a427a73a3ca52397848b5


  • 相关阅读:
    Cobalt Strike 从入门到入狱(三)
    公共事业管理概论试卷6套含答案(大学期末复习资料)
    Window11右键菜单没有新建菜单解决
    python、java、c++哪一个前景比较好?
    免费嵌入 NFT 数据到任何网站或平台
    kotlin基础之协程
    一套极简的MQTT使用接口EasyMqttClient
    无线 LAN 服务概述
    【Excel & PDF 系列】iText 库直接实现表格 PDF
    DOS学习-目录与文件应用操作经典案例-type
  • 原文地址:https://blog.csdn.net/siaok/article/details/133895649