0
点赞
收藏
分享

微信扫一扫

java字节码编程技术(1/10)-常用指令

这部分内存有点抽象,需要了解一些基本的字节码技术以及.class编译的知识,才能更好的深入此系统内存。但一旦掌握后对代码优化调优会有一个质的提升。

一、加载与存储指令

局部变量表操作

[t]load_0:将局部变量表中下标为0的t类型变量加载到操作数栈中。如iload_0,指int类型,aload_o表示this;

[t]store_0:将栈顶的t类型的数据存储到局部变量表中;

[t]load_n:将局部变量表中下标为0~3的t类型变量加载到操作数栈中;

[t]store_n:将栈顶的t类型的数据存储到局部变量表中0~3位置上;

[t]aload_0:将局部变量表中指定数组中下标为0的t类型变量加载到操作数栈中;

[t]astore_0:将栈顶的t类型的数据存储到局部变量表指定数组中下标为0中;

常量池操作,指不可变的

[t]const_n:将常量值n加到中到操作数栈顶中;n取值-1~5占一个字节大小;iconst_m1比较特殊表示-1,aconst_null表示null

bipush_n:将常量值n加到中到操作数栈顶中;n取值-127~128占二个字节大小;

sipush_n:将常量值n加到中到操作数栈顶中;n取值-32768~32767占三个字节大小;

idc_index:其它范围的数字,从常量池加载对应index索引的常量到操作数栈顶中,index<255,占一个字节

idc_w_index:其它范围的数字,从常量池加载对应index索引的常量到操作数栈顶中,index>255,占二个字节

idc2_w_index:其它范围的数字,从常量池加载对应index索引的long, double常量到操作数栈中,占二个字节

二、操作数栈指令

pop:将栈顶元素弹出,pop2专门用来操作long和double

dup:复制栈顶元素并将复制的数据插入栈顶

dup_x1:复制栈顶元素并将复制的数据插入栈顶第二个位置,dup_x2同样;

dup2、dup2_x1、dup2_x2:参考上一条;

swap:交换栈顶两个元素;

三、运算指令

jvm会把对于基础类型只为int(byte,booble,char,shotr)、long、double、float,当两个类型不相等时,会进行向上自动转换,比如int+float,全自动把int改换为float然后用fadd指令来进行相同,但过程中可能会丢精度,在jvm中类型转换的指令是type2type,比如i2f,表示把Int转换为float;

[t]add:将两个同类型的数字相同

[t]sub:减少

[t]div:除法

[t]mul:乘法

[t]rem:取模

[t]neg:

[t]and:与

[t]or:或

[t]xor:非

四、控制转移

简单条件

ifeq: 比较栈顶元素,如果等于0则跳转,比如代码int n=6; if(n>0),则字节码为iload_1 ifle。相应的还有ifne、iflt、ifge、ifgt、ifle;

if_[type]cmpeq:比较栈顶两个type类型元素,如果相等,则跳转,相应的还有if_icmpne、if_icmplt、if_icmpge、if_icmpgt、if_icmple;

if_acmpeq:比较栈顶两个引用变量,如果相等,则跳转,相应的还有if_acmpne;

tableswitch:switch应用,用于值紧凑,自动补齐功能,下标定位,时间复杂度O(1)

ookupswitch:switch应用,用于值稀疏,不自动补齐,二分查找法,时间复杂度O(log n)

复合条件

for

public int sum(int []numbers){

int sum = 0;

for (int nubmer: numbers){

sum +=nubmer;

}

return sum;

}

public int sum(int[] a) {

---初始化sum

iconst_0

istore 2

---初始化for循环条件

aload 1

astore 3

aload 3

arraylength

istore 4

iconst_0

istore 5

---判断是否可以循环

l0

iload 5--$i

iload 4--$len

if_icmpge l1---成立则分支跳转

---设置number=$array[$si]

aload 3

iload 5

iaload

istore 6

---执行加法

iload 2

iload 6

iadd

istore 2

---执行$si-1

iinc 5,1

---跳转到另一次我于

_goto l0

l1

iload 2

ireturn

}

堆栈内容

0

1

2

3

4

5

6

this

numbers

sum

$array

$len

$si

numbers($array[$si])

switch

String类型处理过程也类似,只不过是在switch前先调用.hashCode方法进行转换,然后用lookupswitch。然后再调用和equals比较,防止hash冲突时跳转错误。

switch (n){

case 0: ;

case 1: ;

case 4: ;

case 6: ;

default:break;

}

tableswitch(

0: l0,

1: l0,

2: l0,

3: l0,

4: l0,

5: l0,

6: l0,

default: l0

)

switch (n){

case 0: ;

case 12: ;

case 40: ;

case 60: ;

default:break;

}

lookupswitch(

0: l1,

12: l1,

40: l1,

60: l1,

default: l1

)

switch (s){

case "java": ;

case "c": ;

}

INVOKEVIRTUAL java/lang/String.hashCode ()I

lookupswitch(

99: l2,

3254818: l3,

default: l4

)

l3

aload 3

ldc "java"

INVOKEVIRTUAL java/lang/String.equals (Ljava/lang/Object;)Z

ifeq l4

iconst_0

istore 4

_goto l4

l2

aload 3

ldc "c"

INVOKEVIRTUAL java/lang/String.equals (Ljava/lang/Object;)Z

ifeq l4

iconst_1

istore 4

l4

iload 4

lookupswitch(

0: l5,

1: l5,

default: l5

)

五、自增i++和++i

int i = 0; i = i++;//输出结果为0;

i= ++i; //输出结果为1

iload_0

iinc 0, 1 这个操作是操作局部变量表,并不操作栈顶

istore_0;所以这行相当于把栈顶元素又存回原来位置了

iload_0

iinc0, 1

iload_0 这有个重新调用的过程

istore_0


六、无条件

try catch

public void i(){

try{

tryItOut1();

}catch(Exception e){

handle();

}


}

public void i() {

trycatchblock l0, l1, l2, 'java/lang/Exception'

l0

aload 0

INVOKEVIRTUAL com/jvm / command / TypeTest.tryItOut1() V

l1

_goto l3

l2

astore 1

aload 0

INVOKEVIRTUAL com/jvm / command / TypeTest.handle() V

l3

return

}



Exception Table ---这是class类的内容

from to targe type

0 4 7 java/lang/Exception

这个异常表的意思是执行字节码0到4行,上面的L0,如果出现异常则执行第7行,上面的L2。

finally

源代码

字节码编译过的伪代码

备注

public void i(){

try{

tryItOut1();

}catch(Exception e){

handle();

}finally {

fin();

}


}

public void i(){

try{

fin();

tryItOut1();

}catch(Exception e){

fin();

handle();

}

}

所以finally总会执行

public int i(){

try{

int a = 1/0;

return 0;

}catch(Exception e){

int a = 1/0;

return 1;

}finally {

return 2;

}


}

public int i1(){

try{

int a = 1/0;

int tmp = 0;

return 2;

}catch(Exception e){

try{

int a = 1/0;

int tmp = 1;

return 2;

}catch(Exception e1){

return 2;

}

}

}

受return影响,原来语句中的return这时会把return的值保存在临时变量中,而finally的值才会返回局部变量表中;

public void i1(){

int i = 100;

try{

System.out.println(i);

}finally {

++i;// i++

}

}//全是100

public void i1(){

User user = new User();

user.setName("ld");

try{

System.out.println(user.getName());

}finally {

user.setName("hm");

}

}//ld

这两个输出全是100,因为finally中操作的东西全是解析为临时变量,并不会执行store指令;对象也一样


try with resources

它是防止异常被淹没的一种优先,通过Throwable.java新扩展的addSuppressed方法来实现,这个方法可以调出由于编码错误而被淹没的异常;

//因为没有显示的catch,所以try中的异常会被finally中的异常淹没

public void i1() throws IOException {

FileOutputStream in = null;

try{

in = new FileOutputStream("");

}finally {

in.close();

}

}

public void i11() throws Exception {

FileOutputStream in = null;

FileNotFoundException tmpException= null;

try{

in = new FileOutputStream("");

} catch (FileNotFoundException e) {

tmpException = e;

throw e;

} finally {

if (tmpException != null){

try {

in.close();

} catch (IOException e) {

tmpException.addSuppressed(e);

}

}else{

in.close();

}

}

}


七、对象创建

<init>方法

在编译成字节码时,此方法是自动创建的,他会按顺序把类变量、非静态代码块、构造函数中的方法按顺序包装进此类里;

public class User {

private String name = "ld";


public User(){

int c = 30;

}

{

int b = 20;

}


static{

int d = 40;

}


}

public class com/jvm/command/User {

// access flags 0x2

private Ljava/lang/String; name


@groovyx.ast.bytecode.Bytecode

public void <init>() {

aload 0

INVOKESPECIAL java/lang/Object.<init> ()V

aload 0

ldc "ld"

putfield 'com/jvm/command/User.name','Ljava/lang/String;'

bipush 20

istore 1

bipush 30

istore 1

return

}


@groovyx.ast.bytecode.Bytecode

static void <clinit>() {

bipush 40

istore 0

return

}

}

类的创建过程

类创建要经过三个阶段

public static void main(String []args){

new User();

}


public static void main(String[] a) {

_new 'com/jvm/command/User'//只创建一个引用,并把此引入压入栈顶

dup //因为invokespecial会消费栈顶元素,如果不复制一份引用,则执行完了init方法后,就没地回写了,尤其是构造函数还没有返回值

INVOKESPECIAL com/jvm/command/User.<init> ()V //这时才会执行类的初始化方法

pop //存储到局部变量表中

return

}

static--<cinit>方法

类编译时,会把所有静态的变量、代码块编译时cinit方法中。它会在4个指令下触发

new:比如new、反射、反序列化等;

getstatic:访问类的静态变量;

putstatic:访问类的静态字段或对静态字段赋值,final除外,因为final值使用时是需要复制一份的;

invokestatic:静态方法;

初始化它的子类;因为父类必须要在子类之前OK;但此方法一般只会执行一次

举报

相关推荐

0 条评论