JVM(一)字节码文件

JVM(一)字节码文件

第一节课 Java字节码文件

1.是不是只有Java编译器才能完成Java到class字节码文件的编译过程?

image.png 不是的。Jython/Scala/Groovy/JRuby都是可以编译成字节码文件的

2.class文件的组成

image.png

  • Class文件是一组以8字节为基础单位的二进制流,
  • 各个数据项目严格按照顺序紧凑排列在class文件中,
  • 中间没有任何分隔符,这使得class文件中存储的内容几乎是全部程序运行的程序。

Java虚拟机规范规定,Class文件格式采用类似C语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数

struct Classfile{
​	FiledRef filedRef;
​   MethodRef methodRef;
    ...
}

无符号数

属于基本数据类型,主要可以用来描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值,大小使用u1、u2、u4、u8分别表示1字节、2字节、4字节和8字节。

是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有的表都习惯以“_info”结尾。表主要用于描述有层次关系的复合结构的数据,比如方法、字段。需要注意的是class文件是没有分隔符的,所以每个的二进制数据类型都是严格定义的。

3.字节码的具体解释

3.1 如何查看字节码文件?

image.png

image.png

1.使用notepad++查看字节码文件

  • 使用IDEA编译程序 得到TestApplication.class文件
  • 在target目录下找到TestApplication.class 拷贝它的路径
  • 打开notepad++ 下载插件HEX-Editor
  • 使用notepad++ 打开字节码文件
  • 选择使用插件HEX-Editor插件查看

2.使用javap命令行查看

image.png

javap -v TestApplication.class //-v表示显示指令后面的提示

3.2 class文件详解

分析TestApplication.java类

/**
 * @author amos
 * 与你同在
 * @create 2019/10/19 - 10:49
 */

public class TestApplication extends Object implements Serializable {

    private static int a = 5;

    private String name = "amos";

    public void init() {
        try {
            System.out.println("init....");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        System.out.println(a);
    }
}

3.2.1 常量池

image.png

image.png

image.png

image.png

image.png

image.png

image.png

1.init和clinit的区别

  • init和clinit方法执行时机不同

    init是对象构造器方法,也就是说在程序执行 new 一个对象调用该对象类的 constructor 方法时才会执行init方法,而clinit是类构造器方法,也就是在jvm进行类加载—–验证—-解析—–初始化,中的初始化阶段jvm会调用clinit方法。

  • init和clinit方法执行目的不同

    init is the (or one of the) constructor(s) for the instance, and non-static field initialization. clinit are the static initialization blocks for the class, and static field initialization. 上面这两句是Stack Overflow上的解析,很清楚init是instance实例构造器,对非静态变量解析初始化,而**clinit是class类构造器对静态变量,静态代码块进行初始化。**看看下面的这段程序就很清楚了。

    class X {
       static Log log = LogFactory.getLog(); // <clinit>
       private int x = 1;   // <init>
       X(){
          // <init>
       }
    
       static {
          // <clinit>
       }
    }
    
    
  • 总结

init:实例化的初始化方法

clinit:类和接口的初始化方法

init使用场景:

类的元数据:描述类的数据

2.类名应该有多长?

常量池的CONSTANT_NameAndType_info表 中index为u2 也就是说类名最大不能超过两个字节 也就是256

JVM 对于类的限制是所有的编译后的静态成员字节码、每个方法字节码等长度不能大于 64kB

3.2.2 访问控制标志

image.png

因为ACC_SUPER永远为真所以访问标志一定大于0x0020

如果类的访问控制标志位0x0021,说明这个类访问标志为public 因为0x0020 | 0x0001 = 0x0021

3.2.3 类索引、父类索引与接口索引集合

image.png

0021访问标志public
00 0b类索引demo/TestApplication
00 0c父类索引java/lang/Object
00 01接口计数器实现了一个接口
00 0d接口索引实现的接口为:java/io/Serializable

3.2.4 字段表集合

image.png

image.png

image.png

字段表集合
00 02字段计数器类中有2个字段
第一个字段表
00 0aaccess_flagsprivate static
00 0ename_indexa(字段名称的索引)
00 0fdescriptor_indexI(int)
00 00attribute_count0个属性
第二个字段表
00 02access_flagsprivate
00 10name_indexname
00 11descriptor_indexLjava/lang/String;
00 00attribute_count0个属性

两个字段分别为private static aprivate String name

因为两个字段表的attribute_count都为0所以都没有attributes这个属性表。

3.2.5 方法表集合

image.png

方法表的结构

image.png

3.2.6 属性表的结构

image.png

image.png

3.2.6.1 Code属性

image.png

00 04方法计数器该类中含有4个方法
第一个方法表
00 01access_flagspublic 访问控制标志
00 12name_index 方法的索引名称
00 13descriptor_index:方法描述符V()
00 01attribute_count该方法有一个属性
attribute_infoattributesattribute_count
第一个方法的属性表(attribute_info)
00 14attribute_name_indexa
00 00 00 39attribute_length:属性长度57
00 ~ 00属性表的内容属性表长度为57
图中高亮部分即为属性表的内容

image.png

第二个方法表
00 01access_flagspublic
00 19name_indexinit
00 13descriptor_index:方法描述符()V
00 01attribute_count该方法有一个属性
00 14attribute_name_indexa
00 00 00 6aattribute_length:属性长度106
00 ~04info(属性表的内容)长度为106
第二个方法的属性表的内容

image.png

第三个方法表
00 09access_flagspublic static
00 1ename_indexmain
00 1fdescriptor_index:方法描述符([Ljava/lang/String;)V
00 02attribute_count该方法有两个属性
第一个属性表
00 14attribute_name_indexCode
00 00 00 38attribute_length:属性长度56
00 ~ 00第一个属性表的内容长度为56

image.png

分析第一个属性表(即Code属性表)

image.png

★注意:attribute_name_index和attribute_length在上面 所以查看Code属性的内容时 直接从max_stack开始找。

00 02max_stack2个栈深
00 01max_locals1个本地变量
00 00 00 0acode_length代码长度为10
b2 ~ b1code(代表栈调用情况)代码内容 共u10
00 00exception_table_length异常表长度为0(没有异常)

img

1.分析code(栈调用情况)

image.png

这一部分需要对照:java 虚拟机字节码指令表 搜索16进制代码对应的指令的意义。

  • b2 00 04 代表将00 04号常量压入栈顶

image.png

  • b2 00 09同理
  • b6 00 0a 代表调用实例方法#10 //java/io/PrintStream.println:(I)V

image.png

2.对照javap生成的内容

image.png

3.args_size

  • 对于非静态方法args_size的个数默认为1个(即最少为1个)因为默认的参数是this
  • 对于静态方法 args_size默认个数为0个 因为静态方法不需要this就可以访问。
3.2.6.2 异常表

image.png

image.png

image.png

如果(字节码的)0~11行(实际代码的19-20行)出现异常,则直接跳转到第8(JVM)行(实际上是第22行)

3.2.6.3 本地变量表

image.png

  • start_pc + length为一个本地变量的作用域
  • slot:使用几个槽来存储这个变量
  • name:简单名字
  • signature:Java中采用的是伪泛型,当编译的时候会将泛型擦除,而signature是为了标志这些泛型的。

image.png

3.2.6.4 SourceFile、ConstantValue属性

image.png

3.2.6.5 InnerClasses属性

image.png

3.2.6.6 Deprecated和Synthetic、StackMapTable

image.png

3.2.2.7 Signature、BoostrapMethod

image.png

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×