基本概念
java中的字节码,英文bytecode。是java代码编译后的中间代码格式。JVM需要读取并解析字节码才能执行相应的任务。java字节码是JVM的指令集。JVM加载字节码格式的class文件。校验之后通过JIT编译器转换成本机机器代码执行。
java字节码简介
1、java bytecode由单字节(byte)的指令组成,理论上最多支持256个操作码。实际上java只使用了200左右的操作码,还有一些操作码则保留给调试操作。
2、操作码(指令),主要有“类型前缀”和“操作名称”两部分组成。
3、指令性质分类,主要分成四个大类
--- 栈操作指令,包括与局部变量交互的指令
--- 程序流程控制指令
--- 对象操作指令,包括方法调用指令
--- 算数运算以及类型转换指令
--- 此外还有一些执行专门任务的指令,比如同步(synchronization)指令,以及抛出异常相关的指令等等。
生成字节码
1、javac xxx.java 将java文件编译成class文件
2、javap -c xxx.class(或者xxx) 反编译class文件,获取字节码清单
3、java中,如果不定义任何构造参数,就会有一个默认的无参构造函数。编译后的class文件证实了其中存在默认构造函数。
4、javap -c verbose xxx(指定 -verbose选项,会输入附加信息),java中每个构造函数中都会先调用super类的构造函数。(无参构造函数编译后存在以下指令)
5、Constant pool:常量池就是一个常量的大字典,使用编号的方式把程序里用到的各类常量统一管理起来,这样在字节码操作中,只需要引用编号即可。
查看方法的信息:descriptor: ([Ljava/lang/String;)V
。其中小括号内是入参信息形参信息
。左方括号表示数组
。L表示对象
。后面的 java/lang/String就是类名称
。小括号后面的v则表示这个方法的返回值是void
。方法的访问标志也很容易理解flags: ACC_PUBLIC, ACC_STATIC,表示public和static
。stack=2, locals=2, args_size=1分别表示栈stack深度,局部变量表中保留的槽位数,方法的参数个数
线程栈与字节码执行模型
jvm是一台基于栈的计算机器,每个线程都有一个独属于自己的线程栈(JVM stack),用于存储栈帧(Frame)。每一次方法调用,jvm都会自动创建一个栈帧。栈帧由操作数栈,局部变量数组,一个class引用组成。class引用指向当前方法在运行时常量池中对应的class。
局部变量数组(局部变量表LocalVariableTable):包含了方法的参数以及局部变量。局部变量表的大小在编译的时候就已经确定了。和局部变量表+形参的个数有关,还有每个变量/参数占用多少个字节。
操作数栈:是一个LIFO结构的栈,用于压栈和弹出值,它的大小也在编译的时候确定。有一些指令可以压栈或者从栈中取值操作,还有一些可以接受调用其他方法返回的结果值。
对象初始化指令
当看倒new ,dup , invokespecial指令在一起的时候,那么一定是在创建类的实例对象。
new: 指令只是创建对象,但是没有调用构造方法。
invokespecial: 指令用来调用某些特殊方法的,当然这里调用的是构造函数。
dup: 指令用来复制栈顶的值。由于构造函数调用不会返回值,所以如果没有dup指令,在对象上调用方法并初始化之后,操作数栈就会是空的,在初始化之后就会出现问题,接下来的代码就无法对其进行处理。这就是为什么要事先复制引用的原因,为的是在构造函数返回之后,可以将对象实例 赋值给局部变量或某个字段。因此,接下来的那条指令一般是以下几种: astore {N} or astore_{N} – 赋值给局部变量,其中 {N} 是局部变量表中 的位置。 putfield – 将值赋给实例字段 putstatic – 将值赋给静态字段 在调用构造函数的时候,其实还会执行另一个类似的方法 ,甚至在执行构 造函数之前就执行了。 还有一个可能执行的方法是该类的静态初始化方法 , 但 并不 能被直接调用,而是由这些指令触发的: new , getstatic , putstatic or invokestatic 。 4.8 对象初始化指令:new指令, init 以及 clinit 简介 0: new #2 // class demo/jvm0104/HelloByteCode 3: dup 4: invokespecial #3 // Method "":()V 1 2 3 也就是说,如果创建某个类的新实例, 访问静态字段或者调用静态方法,就会触发该 类的静态初始化方法【如果尚未初始化】。
栈内存操作指令
dup 指令复制栈顶元素的值。 pop 指令则从栈中删除最顶部的值。
swap 指令可交换栈顶两个元素的值;
dup_x1 将复制栈顶元素的值,并在栈顶插入两次;
dup2_x1 则复制栈顶两个元素的值,并插入第三个值。
局部变量表
待学习
流程控制指令
待学习
算术运算指令与类型转换指令
待学习
方法调用指令和参数传递
待学习
JDK1.7新增方法调用指令invokedynamic
待学习