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

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

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

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

无关性的基石

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

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


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

字节码是无关性的基石

平台无关性的基石:

  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查看反编译的后果


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

Class文件格式的形容
数据类型名称数量对应图中名字作用
u4magic1魔数确定这个文件是否是一个能被虚拟机承受的Class文件
u2minor_version1次版本号虚拟机必须拒绝执行超过其版本号的Class文件
u2major_version1主版本号虚拟机必须拒绝执行超过其版本号的Class文件
u2constant_pool_count1常量池容量计数器统计常量数量
cp_infoconstant_poolconstant_pool_count - 1常量池寄存常量
u2access_flags1拜访标记辨认类(类,接口)的访问信息
u2this_class1类索引确定类的全限定名
u2super_class1父类索引确定父类的全限定名
u2interfaces_count1接口计数器统计该类实现接口数量
u2interfacesinterfaces_count接口索引汇合形容该类实现了的接口
u2fields_count1字段表汇合计数器统计类的字段数量
field_infofieldsfields_count字段表汇合形容类申明的字段(类变量,实例变量)
u2methods_count1办法表汇合计数器统计类的办法数量
method_infomethodsmethods_count办法表汇合形容类申明的办法
u2attribute_count1属性表汇合计数器统计属性数量
attribute_infoattributesattributes_count属性表汇合形容属性

魔数与主次版本号

  • 魔数: 确定这个文件是否为一个能被虚拟机承受的无效Class文件
  • 主次版本号: 虚拟机拒绝执行超过其版本号的Class文件

    • 不同版本的Java前端编译器编译生成对应的Class文件主次版本号不同
    • 反对高版本JVM执行低版本前端编译器生成的Class文件(向下兼容)
    • 回绝低版本JVM执行高版本前端编译器生成的Clsss文件

常量池

常量池蕴含两大常量: 字面量和符号援用

符号援用与间接援用
  • 符号援用

    • 应用一组符号形容援用(为了定位到指标援用)
    • 与虚拟机内存布局无关
    • 还是符号援用时指标援用不肯定被加载到内存
  • 间接援用

    • 间接执行指标的指针,绝对偏移量或间接定位指标援用的句柄
    • 与虚拟机内存布局相干
    • 解析间接援用时指标援用曾经被加载到内存中
字面量与符号援用
  • 字面量

    • 文本字符串
    • 被final申明的常量
  • 符号援用

    • 全限定名
    • 办法或字段的简略名称和描述符


图中的常量有咱们代码中相熟的常量也有很多没有显示呈现在代码中的常量

拜访标记

用于辨认类或接口的访问信息

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

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


ACC_PUBLIC:被public润饰

ACC_SUPER: 容许应用invokespecial字节码指令

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

类索引

用于确定本类的全限定名


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

父类索引

用于确定父类的全限定名

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

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

接口索引汇合

形容这个类实现了哪些接口

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

总结

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

字段表汇合

形容类申明的字段

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

简略名称和描述符
  • 简略名称

    • 字段: 没有形容字段类型的名称
    • 办法: 没有形容参数列表和返回类型的名称
  • 描述符

    • 字段: 形容字段的类型
    • 办法: 形容参数列表和返回值
    • 描述符字符含意(long,boolean,对象类型是J,Z,L 其余都是首字母大写)

      标识字符含意
      Bbyte
      Cchar
      Ddouble
      Ffloat
      Iint
      Jlong
      Sshort
      Zboolean
      Vvoid
      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语法 规定 重载 = 简略名称雷同 + 描述符的参数列表不同 + 描述符的返回类型不能不同

办法表汇合

形容类申明的办法

与字段表汇合相似

留神

办法表汇合中不会列出父类办法信息(不重写的状况)

属性表汇合

属性比拟多,这里只阐明咱们例子中呈现的,其余的会总结

用于形容某些场景专有信息

刚刚在字段,办法表汇合中都能够看到属性表汇合,阐明属性表汇合是能够被携带的

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

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

Code属性

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


其中的异样表汇合代表 编译器为这段代码生成的多条异样记录,对应着可能呈现的代码执行门路

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

Exceptions属性

列举出办法中可能抛出的查看异样(Checked Exception),也就是办法申明throws关键字前面的列举异样

LineNumberTable属性

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

SourceFile属性

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

StackMapTable属性

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

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

ConstantValue

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

只有类变量才有这个属性

实例变量的赋值: 在实例结构器<init>

类变量的赋值: 在类结构器<clinit>或 带有ConstantValue属性在类加载的筹备阶段

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

![image-20201107191419341]()

总结所有属性
属性名作用
Code办法体内的代码通过编译后变为字节码指令存储在Code属性中
Exceptions列举出办法可能抛出的查看异样(Checked Exception)
LineNumberTableJava源码行号与字节码偏移量(字节码行号)对应关系
LocalVariableTableJava源码定义的局部变量与栈帧中局部变量表中的变量对应关系(局部变量名称,描述符,局部变量槽地位,局部变量作用范畴等)
LocalVariableTypeTableLocalVariableTable类似,只是把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

罕用

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);     } }

最初(不要白嫖,一键三连求求拉\~)

本篇文章笔记以及案例被支出 gitee-StudyJava、 github-StudyJava 感兴趣的同学能够stat下继续关注喔\~

有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下\~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 公布!