深入浅出JVM(四)之类文件结构

简介: 深入浅出JVM(四)之类文件结构

Java文件编译成字节码文件后,通过类加载机制到Java虚拟机中,Java虚拟机能够执行所有符合要求的字节码,因此无论什么语言,只要能够编译成符合要求的字节码文件就能够被Java虚拟机执行

Java虚拟机和字节码是语言、平台无关性的基石

本篇文章将深入浅出的解析字节码文件

无关性的基石

曾经: 源代码->经过编译->本地机器码

Java: 源代码->经过编译->字节码 -> 解释器 -> 本地机器码

image.png

字节码: 与操作系统和机器指令集无关的,平台中立的程序编译后的存储格式

字节码是无关性的基石

平台无关性的基石:

  1. 所有平台都统一支持字节码
  2. 不同的Java虚拟机都可以执行平台无关的字节码

因此实现了 一次编译,到处运行

语言无关性的基石:

  1. Java虚拟机
  2. 字节码

Java虚拟机不是只可以执行Java源代码编译而成的字节码,只要符合要求(安全...)的字节码,它都可以执行

因此Kotlin...等语言可以运行在Java虚拟机上

Class类文件结构

文件格式存取数据的类型

  1. 无符号数 : u1,u2,u4,u8代表1,2,4,8个字节的无符号数(可以表示数字,UTF-8的字符串,索引引用....)
  2. 表: 由n个无符号数或n个表组成(命名以_info结尾)

初识Class文件格式

编写Java源代码

public class Test {
     private int m;
     private final int CONSTANT=111;
 
     public int inc() throws Exception {
         int x;
         try {
             x = 1;
             return x;
         }catch (Exception e){
             x = 2;
             return  x;
         }finally{
             x = 3;
         }
     }
 }

使用可视化工具classpy查看反编译的结果

image.png

每个集合前都有一个计数器来统计集合中元素的数量

Class文件格式的描述

数据类型 名称 数量 对应图中名字 作用
u4 magic 1 魔数 确定这个文件是否是一个能被虚拟机接受的Class文件
u2 minor_version 1 次版本号 虚拟机必须拒绝执行超过其版本号的Class文件
u2 major_version 1 主版本号 虚拟机必须拒绝执行超过其版本号的Class文件
u2 constant_pool_count 1 常量池容量计数器 统计常量数量
cp_info constant_pool constant_pool_count - 1 常量池 存放常量
u2 access_flags 1 访问标志 识别类(类,接口)的访问信息
u2 this_class 1 类索引 确定类的全限定名
u2 super_class 1 父类索引 确定父类的全限定名
u2 interfaces_count 1 接口计数器 统计该类实现接口数量
u2 interfaces interfaces_count 接口索引集合 描述该类实现了的接口
u2 fields_count 1 字段表集合计数器 统计类的字段数量
field_info fields fields_count 字段表集合 描述类声明的字段(类变量,实例变量)
u2 methods_count 1 方法表集合计数器 统计类的方法数量
method_info methods methods_count 方法表集合 描述类声明的方法
u2 attribute_count 1 属性表集合计数器 统计属性数量
attribute_info attributes attributes_count 属性表集合 描述属性

显示详细信息

魔数与主次版本号

  • 魔数: 确定这个文件是否为一个能被虚拟机接受的有效Class文件
  • 主次版本号: 虚拟机拒绝执行超过其版本号的Class文件
  • 不同版本的Java前端编译器编译生成对应的Class文件主次版本号不同
  • 支持高版本JVM执行低版本前端编译器生成的Class文件(向下兼容)
  • 拒绝低版本JVM执行高版本前端编译器生成的Clsss文件

常量池

常量池包含两大常量: 字面量和符号引用

符号引用与直接引用

  • 符号引用
  • 使用一组符号描述引用(为了定位到目标引用)
  • 与虚拟机内存布局无关
  • 还是符号引用时目标引用不一定被加载到内存
  • 直接引用
  • 直接执行目标的指针,相对偏移量或间接定位目标引用的句柄
  • 与虚拟机内存布局相关
  • 解析直接引用时目标引用已经被加载到内存中

字面量与符号引用

  • 字面量
  • 文本字符串
  • 被final声明的常量
  • 符号引用
  • 全限定名
  • 方法或字段的简单名称和描述符

image.png

图中的常量有我们代码中熟悉的常量也有很多没有显示出现在代码中的常量

访问标志

用于识别类或接口的访问信息

是否是一个接口,枚举,模块,注解...

是否被final(public,abstract...)修饰

image.png

ACC_PUBLIC:被public修饰

ACC_SUPER: 允许使用invokespecial字节码指令

类索引,父类索引与接口索引集合

类索引

用于确定本类的全限定名

image.png

类索引指向常量池中表示该类的符号引用

父类索引

用于确定父类的全限定名

image.png

父类索引指向常量池中表示该类父类的符号引用

除了Object外,所有类的父类索引都不为0

接口索引集合

描述这个类实现了哪些接口

我们的例子中没有实现接口,就没有(接口索引集合计数器为0)

总结

Class文件由 类索引,父类索引,接口索引集合 来确定该类的继承关系

字段表集合

描述类声明的字段

字段包括类变量和成员变量(实例变量),不包括局部变量

image.png

简单名称和描述符

  • 简单名称
  • 字段: 没有描述字段类型的名称
  • 方法: 没有描述参数列表和返回类型的名称
  • 描述符
  • 字段: 描述字段的类型
  • 方法: 描述参数列表和返回值
  • 描述符字符含义(long,boolean,对象类型是J,Z,L 其他都是首字母大写)
标识字符 含义
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 对象类型,如Ljava/lang/Object
  • 显示详细信息
  • 描述符描述n维数组
  • 在前面先写n个[ 再写标识字符
    比如java.lang.Integer[ ] => [Ljava.lang.Integer
  • 描述符描述方法
  • 参数列表按照从左到右的顺序写在()
  • 返回类型写到最后
    比如String method(long[],int,String[]) => ([JIL[java.lang.String)Ljava.lang.String

因此Class文件中字段描述符指向常量池中的#07 I 符号引用(的索引)

注意

  1. 字段表集合不会列出父类或父接口中声明的字段
  2. 只用 简单名称 来确定字段,所以不能有重名字段
  3. 用 简单名称 和 描述符 确定方法,所以方法可以重名(重载)
  • 字节码文件 规定 简单名称+描述符相同才是同一个方法
  • 但是 Java语法 规定 重载 = 简单名称相同 + 描述符的参数列表不同 + 描述符的返回类型不能不同

方法表集合

描述类声明的方法

与字段表集合类似

image.png

注意

方法表集合中不会列出父类方法信息(不重写的情况)

属性表集合

属性比较多,这里只说明我们例子中出现的,其他的会总结

用于描述某些场景专有信息

刚刚在字段,方法表集合中都可以看到属性表集合,说明属性表集合是可以被携带的

怎么没看到Java源代码中的代码呢?

实际上它属于属性表集合中的Code属性

Code属性

Java源代码中方法体中的代码经过编译后编程字节码指令存储在Code属性内

image.png

其中的异常表集合代表 编译器为这段代码生成的多条异常记录,对应着可能出现的代码执行路径

(程序在try中不抛出异常会怎么执行,抛出异常又会怎么执行....)

image.png

Exceptions属性

列举出方法中可能抛出的检查异常(Checked Exception),也就是方法声明throws关键字后面的列举异常

image.png

LineNumberTable属性

描述Java源码行号与字节码指令行号(字节码偏移量)对应关系

SourceFile属性

记录生成此Class文件的源码名称

StackMapTable属性

虚拟机类加载验证阶段的字节码验证时,不需要再检验了,只需要查看StackMapTable属性中的记录是否合法

编译阶段将一系列的验证类型结果记录在StackMapTable属性中

image.png

ConstantValue

在类加载的准备阶段,为静态变量(常量)赋值

只有类变量才有这个属性

实例变量的赋值: 在实例构造器

类变量的赋值: 在类构造器或 带有ConstantValue属性在类加载的准备阶段

如果类变量被final修饰(此时该变量是一个常量),且该变量数据类型是基本类型或字符串,就会生成ConstantValue属性,该属性指向常量池中要赋值的常量,在类加载的准备阶段,直接把在常量池中ConstantValue指向的常量赋值给该变量

image-20201107191419341

总结所有属性
属性名 作用
Code 方法体内的代码经过编译后变为字节码指令存储在Code属性中
Exceptions 列举出方法可能抛出的检查异常(Checked Exception)
LineNumberTable Java源码行号与字节码偏移量(字节码行号)对应关系
LocalVariableTable Java源码定义的局部变量与栈帧中局部变量表中的变量对应关系(局部变量名称,描述符,局部变量槽位置,局部变量作用范围等)
LocalVariableTypeTable LocalVariableTable相似,只是把LocalVariableTable的描述符换成了字段的特征签名(完成对泛型的描述)
SourceFile 记录生成这个Class文件的源码文件名称
SourceDebugExtension 用于存储额外的代码调式信息
ConstantValue 在类加载的准备阶段,为静态变量(常量)赋值
InnerClasses 记录内部类与宿主类之间的关系
Deprecated 用于表示某个字段,方法或类已弃用 (可以用注解@deprecated表示)
Synthetic 用于表示某字段或方法不是由Java源代码生成的,而是由编译器自行添加的
StackMapTable 虚拟机类加载验证阶段的字节码验证时,不需要再检验了,只需要查看StackMapTable属性中的记录是否合法
Signature 记录泛型签名信息
BootstrapMethods 保存动态调用(invokeeddynamic)指令引用的引导方法限定符
MethodParameters 记录方法的各个形参名称与信息

显示详细信息

javap解析Class文件

关于javac

javac xx.java 编译Java源文件,不会生成对应的局部变量表

javac -g xx.java 编译Java源文件,生成对应的局部变量表

idea中编译Java源文件使用的是javac -g

关于javap

image.png

常用

javap -v 基本上可以反汇编出Class文件中的很多信息(常量池,字段集合,方法集合...)

但是它不会显示私有字段或方法的信息,所以可以使用javap -v -p

详解javap -v -p

public class JavapTest {
     private int a = 1;
     float b = 2.1F;
     protected double c = 3.5;
     public  int d = 10;
 
     private void test(int i){
         i+=1;
         System.out.println(i);
     }
 
     public void test1(){
         String s = "test1";
         System.out.println(s);
     }
 }

image.png

image.png

image.png


相关文章
|
27天前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
21 3
|
3月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
106 35
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
2月前
|
存储 算法 Java
聊聊jvm的内存结构, 以及各种结构的作用
【10月更文挑战第27天】JVM(Java虚拟机)的内存结构主要包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和运行时常量池。各部分协同工作,为Java程序提供高效稳定的内存管理和运行环境,确保程序的正常执行、数据存储和资源利用。
59 10
|
2月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
36 3
|
2月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
67 3
|
2月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
59 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
4月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
34 3
|
4月前
|
存储 安全 Java
JVM内存结构
这篇文章详细介绍了Java虚拟机(JVM)的内存结构,包括类的加载过程、类加载器的双亲委派机制、沙箱安全机制、程序计数器、Java栈、Java堆、本地方法和本地方法栈等关键组件及其作用。
JVM内存结构
|
4月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
238 0
|
5月前
|
Java
jmap 查看jvm内存大小并进行dump文件内存分析
jmap 查看jvm内存大小并进行dump文件内存分析
123 3