类文件结构

六月ゝ 毕业季﹏ 提交于 2020-01-17 13:36:28

在这里插入图片描述
在这里插入图片描述
Class文件格式只有两种数据类型:无符号数和表。

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

② 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述由层次关系的复合结构的数据,整个Class文件本质上就是一张表。

  1. 魔数:每个class文件的头4个字节(0-3字节)(16进制),用来确定文件是否为一个可以被虚拟机接受的class文件,不用文件扩展名作为标识是因为扩展名可以随意改动
    在这里插入图片描述

  2. 第4,5字节是次版本号;6,7字节是主版本号(JDK向下兼容以前版本的class文件)(0034是java 8)

  3. 8-9字节表示常量池的长度,0023表示常量池有#1~#34项(#0项不计入,也没有值)
    在这里插入图片描述
    常量池是在*.class文件中的一张表,常量池中存放两大常量项:
    ① 字面量(文本字符串,声明为final的常量值);
    ② 符号引用(类和接口的全限定名 (Fully Qualified Name)、字段的名称和描述符、方法的名称和描述符);
    虚拟机根据这张表找到要执行的类名,方法名,参数类型,字面量等信息,
    当类被加载,常量池信息会被放入运行时常量池,并且把里面的符号地址变为真实地址

  4. 常量池后紧跟着的两个字节是访问标识,用于识别类或者接口层次的访问信息(看是哪几项加起来)
    在这里插入图片描述

  5. 类索引、父类索引与接口索引集合

  6. 接下来两个字节是Field成员变量信息,字段包括了类级变量或实例级变量,但不包括在方法内声明的变量。字段的名字、数据类型、修饰符等都是无法固定的,只能引用常量池中的常量来描述
    在这里插入图片描述

  7. methd
    方法的定义可以通过访问标识,名称索引,描述符索引表达清除,方法中的代码经编译器编译成字节码指令后,存在方法属性表集合中一个名为code的属性

字节码指令

java内部的解释器可以识别平台无关的字节码指令,解释为机器码
在这里插入图片描述

javap工具反编译

执行流程

  1. 原始的代码
    在这里插入图片描述

  2. 编辑以后生成.class文件

  3. java虚拟机的类加载器把编译的类进行类加载操作,把.class读取到内存,其中常量池的数据会被放到运行时常量池(小的数值存在方法的代码里,但是一旦数值超过了整数的最大值,就会被存储到常量池)
    在这里插入图片描述

  4. 方法字节码载入方法区
    在这里插入图片描述

  5. main线程运行,分配栈帧内存(里面有局部变量表(所占的内存大小由局部变量表长度度)和操作数栈(最大操作数栈的深度))
    在这里插入图片描述

  6. 执行引擎读取方法的字节码指令开始运行
    在这里插入图片描述
    在这里插入图片描述
    加法运算是把从局部变量表取值放到操作数栈,再从操作数栈依次弹出相加结果存入操作数栈,赋值的时候再把操作数栈的运算结果存到局部变量表的对应槽位

对对象的读取是把堆中的对象的引用放到操作数栈

分析i++

在这里插入图片描述
对于自增运算是用iinc指令,iinc是在局部变量表的slot上进行运算
a++和++a的区别在于
① a++是先执行iload(从局部变量表取数据到操作数栈),再在局部变量表上进行iinc)
① ++a是先执行iinc(在局部变量表上进行iinc)再从局部变量表取数据到操作数栈

bipush 10
在这里插入图片描述
istore_1
在这里插入图片描述
接下来执行a++
① 先iload_1
在这里插入图片描述
② 再iinc
在这里插入图片描述
接下来是++a
① 先iinc
在这里插入图片描述
② 再iload
在这里插入图片描述
a++ + ++a再从操作数栈弹值相加

x = x++分析

永远等于0
① 先从局部变量表取x放到操作数栈
② 局部变量表的x自增
③ 把操作数栈的x放到局部变量表x的槽位

条件判断指令

在这里插入图片描述
goto用来跳转到指定行号的字节码
在这里插入图片描述

循环控制指令

while
在这里插入图片描述
do while
在这里插入图片描述
for和while的字节码完全一样
在这里插入图片描述

构造方法

  1. ()
    编译器会按从上到下的顺序收集所有的static静态代码块和静态成员赋值的代码,合并成一个特殊的方法(),并在类加载的初始阶段被调用
    在这里插入图片描述
    在这里插入图片描述

  2. ()构造方法
    是实例的构造方法,编译器会按照从上到下的顺序,收集所有{}代码块和成员变量的赋值代码,形成新的构造方法,但原始构造方法内的代码总在最后
    在这里插入图片描述
    在这里插入图片描述

方法调用

在这里插入图片描述

  1. 调用构造方法,私有方法,final方法使用的都是invokespecial
  2. 调用普通方法是invokevirtual
  3. 调用静态方法使用的是invokestatic
    在这里插入图片描述
    对于invokespecial和invokesatic是静态绑定,在编译的时候就知道方法的地址,性能高,因为是final,构造,静态,私有

而invokevirtual是动态绑定,可能会方法重写,不知道会去调用谁的

静态方法对于字节码来说不用通过对象来调用,如果通过对象会多一个入操作数栈和出操作数栈的操作,之间用类名调用就好
在这里插入图片描述

多态的原理

通过一个对象的invokevirtual调用方法的时候

① 会先通过栈帧中的对象引用找到对象

② 分析对象头,找到对象的实际的class

③ 对象的class结构里面有一个vtable虚方法表,里面存的都是这个类的多态方法(私有方法,静态方法,final方法不会在),是在类加载的链接阶段就根据方法的重写规则写好了的

④ 根据这个表就可以得到你要调用的方法的具体地址(重写了父类的方法就是自己的方法,没重写就是父类的)(是动态的查找)

⑤ 执行方法的字节码

异常处理

在这里插入图片描述
在这里插入图片描述
有一个Exception table[from,to),一旦这个范围内的字节码执行出现异常,则通过type匹配异常类型,如果一致,进入target所指行号,错误信息e会存入局部变量表
在这里插入图片描述
多个catch在Exception table由多项,但是因为只能匹配一个catch,所以每个catch的错误信息e会存入局部变量表的同一个槽位

finally
在这里插入图片描述
就是把finally的代码拷贝到了try和catch块里
在这里插入图片描述
但是如果catch没有捕获到异常,try和catch的finally就没有执行,所以在异常表里面会检测剩余的异常进行finally的处理,并且在finally里会把这个异常抛出
在这里插入图片描述
练习1
finally出现了retrun不管有没有异常返回的都是fianlly的return
在这里插入图片描述
但是注意,如果在finally里写了return,catch没有捕捉到异常,最后的finally不会抛出这个异常,少了athrow的操作,所以finally里一定不要return,可以试试下面的代码吗,完全正常执行
在这里插入图片描述
练习2
在这里插入图片描述
输出结果是10
在这里插入图片描述
如果在try中return i 又在finally里改变了i,其实fianlly对return的i不会有影响,因为try中先对要return的i进行了暂存,固定了返回值,最后再把暂存放到栈顶进行返回

synchronized

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!