那种网站建设软件最好,大量增加告权重网站友链回提升网站权重吗,管理系统软件,外贸网络营销公司提示#xff1a;文章写完后#xff0c;目录可以自动生成#xff0c;如何生成可参考右边的帮助文档 文章目录 一、Java虚拟机的组成二、字节码文件的组成2.1 为什么要了解字节码文件#xff1f;2.2 如何“窥探”字节码文件的奥秘#xff1f;2.2.1 使用工具打开字节码文件2.… 提示文章写完后目录可以自动生成如何生成可参考右边的帮助文档 文章目录 一、Java虚拟机的组成二、字节码文件的组成2.1 为什么要了解字节码文件2.2 如何“窥探”字节码文件的奥秘2.2.1 使用工具打开字节码文件2.2.2 字节码文件是由哪几部分组成2.2.3 基础信息(一般信息)2.2.4 常量池2.2.5 方法 参考目录 提示以下是本篇文章正文内容下面案例可供参考
一、Java虚拟机的组成
它可以分为以下四个部分
类加载器(ClassLoder)加载class字节码文件中的内容到内存中(加载到内存是为了高效的利用)运行时数据区域(JVM管理的内存)负责管理JVM使用到的内存比如创建对象和销毁对象执行引擎((即时编译器、解释器与垃圾回收器等)将字节码文件中的指令解 释成机器码同时使用即时编译器优化性能本地接口调用本地已经编译的方法比如虚拟机中提供的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插件版的两个小细节 要打开一个新的字节码文件就需要选中当前的源代码然后点击 view - show bytecode with jclasslib 就可以打开新的字节码文件 如果源代码发生变化需要重新运行编译可通过 build - Recompile ‘xxx.java’ 或 重新运行该源代码的方式重新编译然后在jclasslib中重新刷新即可 ②使用Javap命令 如何使用该命令查看指定字节码文件
步骤 首先确保已经安装了JDKJava Development Kit因为Javap是JDK的一部分。 打开命令提示符Windows或终端macOS/Linux。 使用cd命令导航到包含字节码文件.class文件的目录。例如如果字节码文件位于C:\Users\YourUsername\Documents\MyProject目录下请输入cd C:\Users\YourUsername\Documents\MyProject。 输入javap -c YourClassName.class命令其中YourClassName是你要查看的字节码文件的名称不包括扩展名。例如如果你要查看名为MyClass.class的文件请输入javap -c MyClass.class。 按回车键执行命令。命令将显示字节码文件的详细信息包括类名、方法名、参数类型等。 案例使用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.3JDK1.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 i0;int j i1;字节码指令分析如下 回到前面提到的面试题
为什么int i 0; i i; 最终i的值是0
源代码示例如下
public static void main(String[] args) {int i0;ii;System.out.println(i i);
}分析流程如下所示 举例分析 以 int i0; ii; 的字节码指令展开分析最后i的值是多少 示例代码如下 public static void main(String[] args) {int i0;ii;System.out.println(i i);}字节码指令如下 0 iconst_01 istore_12 iinc 1 by 15 iload_16 istore_17 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
流程分析如下
①将int类型的 o push 操作数栈中 ②从操作数栈中取出0放入到局部变量表中位序为1的位置上[i]此时i0 ③在局部变量表中为位序为1的位置上增加1此时i1 ④从局部变量表中位序为1的位置将数据压入到操作数栈中此时i0 ⑤将操作数栈中的数据[1]保存到局部变量表中位序为1的位置上此时i1 …
备注 如果不清楚某一条指令的作用可采取以下步骤 ①选中指令点击“显示JVM规范” ②浏览器会自动跳转至对应指令的详情页面 参考目录
https://www.bilibili.com/video/BV1r94y1b7eS?p7spm_id_frompageDrivervd_source5a34715e416a427a73a3ca52397848b5