JVM - 字节码执行

此博客为炼数成金JVM教程第十一课

目录

  1. javap
  2. 简单的字节码执行过程
  3. 常用的字节码
  4. 使用ASM生成java字节码
  5. JIT及其相关参数

javap
     class 文件反编译工具

有如下代码:

public class Calc{
    public int calc(){

        int a = 500;
        int b = 200;
        int c = 50;
        return (a+b)/c;
    }

}

用javap 反编译

javac Calc.java
javap -verbose Calc

结果:

Classfile /C:/Users/Administrator/Desktop/code/Calc.class
  Last modified 2018-6-15; size 262 bytes
  MD5 checksum 47226419c37bb8f7e29890883f8a5807
  Compiled from "Calc.java"
public class Calc
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#12         // java/lang/Object."<init>":()V
   #2 = Class              #13            // Calc
   #3 = Class              #14            // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               calc
   #9 = Utf8               ()I
  #10 = Utf8               SourceFile
  #11 = Utf8               Calc.java
  #12 = NameAndType        #4:#5          // "<init>":()V
  #13 = Utf8               Calc
  #14 = Utf8               java/lang/Object
{
  public Calc();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public int calc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: sipush        500
         3: istore_1
         4: sipush        200
         7: istore_2
         8: bipush        50
        10: istore_3
        11: iload_1
        12: iload_2
        13: iadd
        14: iload_3
        15: idiv
        16: ireturn
      LineNumberTable:
        line 4: 0
        line 5: 4
        line 6: 8
        line 7: 11
}
SourceFile: "Calc.java"

具体我们看

public int calc();
    Code:
      stack=2, locals=4, args_size=1
         0: sipush        500
         3: istore_1
         4: sipush        200
         7: istore_2
         8: bipush        50
        10: istore_3
        11: iload_1
        12: iload_2
        13: iadd
        14: iload_3
        15: idiv
        16: ireturn
}

其中 1 3 4 … 16,对于字节码来说是偏移量,广义上来说可以理解为行号
这些行代表的是:方法运行时需要运行的指令

上述字节码执行过程
我们需要关注:
    程序计数器:每个线程都有一个单独的计数器指向当前正在执行的    指令的位置
    局部变量表
    操作数栈

执行 sipush
  该方法为实例方法,this作为第一个参数放在局部变量表,
  sipush 把500 放入到操作数栈

执行istore_1
  从操作数栈弹出一个数字到局部变量表
  操作数栈被清空,局部变量表存储500
这里写图片描述

执行sipush 200
  把200 压入操作数栈

istore_2
  把200放入局部变量表,操作数栈被清空
这里写图片描述

执行bipush 50
  把50 压入操作数栈

istore_3
  把50放入局部变量表,操作数栈被清空
这里写图片描述

执行iload_1
  把局部变量表的第一个位置的整数数据压入到操作数栈

执行iload_2
  把局部变量表的第二个位置的整数数据压入到操作数栈

这里写图片描述

执行iadd
  将两个数从操作数栈中弹出,做相加操作,再将相加的值压入操作数栈

执行iload_3
  将局部变量表第三个位置的整数数据压入到操作数栈
这里写图片描述

执行idiv
  从操作数栈中弹出两个数,做除法,将结果压入操作数栈

执行ireturn
  方法体的返回,返回值为操作数栈的栈顶的元素
这里写图片描述

这里写图片描述

字节码指令为一个byte整数
左半部分:注解符,帮助理解
右半部分:在程序中真正的表示方式

_nop                  =   0, // 0x00
_aconst_null          =   1, // 0x01
_iconst_0             =   3, // 0x03
_iconst_1             =   4, // 0x04
_dconst_1             =  15, // 0x0f
_bipush               =  16, // 0x10
_iload_0              =  26, // 0x1a
_iload_1              =  27, // 0x1b
_aload_0              =  42, // 0x2a
_istore               =  54, // 0x36
_pop                  =  87, // 0x57
_imul                 = 104, // 0x68
_idiv                 = 108, // 0x6c

void serAge(int) 方法的字节码:
2A 1B B5 00 20 B1

2A _aload_0
    无参
    将局部变量slot0 作为引用 压入操作数栈
1B _iload_1
    无参
    将局部变量slot1 作为整数 压入操作数栈
B5 _putfield
    设置对象中字段的值
    参数为        2bytes (00 20) (指明了字段)
        指向常量池        的引用
        Constant_Fieldref
        此处为User.age
    弹出栈中2个对象:objectref, value
    将栈中的value赋给objectref的给定字段
B1 _return

常量入栈
  aconst_null null对象入栈
  iconst_m1 int常量-1入栈
  iconst_0 int常量0入栈
  iconst_5 int常量5入栈
  lconst_1 long常量1入栈
  fconst_1 float 1.0入栈
  dconst_1 double 1.0 入栈
  bipush 8位带符号整数入栈
  sipush 16位带符号整数入栈
  ldc 常量池中的项入栈

局部变量压栈
xload(x为i l f d a)
        分别表示int,long,float,double,object ref
xload_n(x为i l f d a,n为0 1 2 3)
        表示第几个变量入栈
xaload(x为i l f d a b c s)
        分别表示int, long, float, double, obj ref ,byte,char,short
        从数组中取得给定索引的值,将该值压栈
        iaload
                执行前,栈:…, arrayref, index
                它取得arrayref所在数组的index的值,并将值压栈
                执行后,栈:…, value

出栈装载入局部变量
xstore(x为i l f d a)
   出栈,存入局部变量
xstore_n(x为i l f d a, n 0 1 2 3)
  出栈,将值存入第n个局部变量
xastore(x为i l f d a b c s)
  将值存入数组中
    iastore
       执行前,栈:…,arrayref, index, value
       执行后,栈(被清空):…
       将value存入arrayref[index]

通用栈操作(无类型)
  nop 什么都不做
  pop
    弹出栈顶1个字长
  dup
    复制栈顶1个字长,复制内容压入栈

类型转换

  • i2l
  • i2f
  • l2i
  • l2f
  • l2d
  • f2i
  • f2d
  • d2i
  • d2l
  • d2f
  • i2b
  • i2c
  • i2s

例子:i2l
将int转为long
执行前,栈:…, value
执行后,栈:…,result.word1,result.word2
弹出int,扩展为long,并入栈

整数运算

  • iadd
  • ladd
  • isub
  • lsub
  • idiv
  • ldiv
  • imul
  • lmul
  • iinc(++)

浮点运算

  • fadd
  • dadd
  • fsub
  • dsub
  • fdiv
  • ddiv
  • fmul
  • dmul

对象操作指令
  new 生成对象
  getfield 从对象中拿出给定的值
  putfield 从对象中设置给定的值
  getstatic 针对静态的字段拿值
  putstatic 针对静态的字段设置值

条件控制
  ifeq 如果为0,则跳转
  ifne 如果不为0,则跳转
  iflt 如果小于0 ,则跳转
  ifge 如果大于0,则跳转
  if_icmpeq 如果两个int相同,则跳转

例子:ifeq
参数 byte1,byte2
value出栈 ,如果栈顶value为0则跳转到(byte1<<8)|byte2
执行前,栈:…,value
执行后,栈:…

方法调用
  invokevirtual: 对一个普通实例的类方法进行调用(动态绑定)
  invokespecial: 对父类的方法进行调用(静态绑定)
  invokestatic: 对静态的方法进行调用
  invokeinterface: 对接口方法进行调用
  xreturn(x为 i l f d a 或为空): 方法的返回

使用ASM生成Java字节码

ASM
  jva字节码操作框架
  可以用于修改现有类或者动态产生新类
  用户:
    AspectJ
    Clojure
    Eclipse
    spring
    cglib
       hibernate

代码实现例子:

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES);  
cw.visit(V1_7, ACC_PUBLIC, "Example", null, "java/lang/Object", null);  
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,  null);  
mw.visitVarInsn(ALOAD, 0);  //this 入栈
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");  
mw.visitInsn(RETURN);  
mw.visitMaxs(0, 0);  
mw.visitEnd();  
mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",  "([Ljava/lang/String;)V", null, null);  
mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out",  "Ljava/io/PrintStream;");  
mw.visitLdcInsn("Hello world!");  
mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",  "(Ljava/lang/String;)V");  
mw.visitInsn(RETURN);  
mw.visitMaxs(0,0);  
mw.visitEnd();  
byte[] code = cw.toByteArray();  
AsmHelloWorld loader = new AsmHelloWorld();  
Class exampleClass = loader  
    .defineClass("Example", code, 0, code.length);  
exampleClass.getMethods()[0].invoke(null, new Object[] { null }); 

模拟实现AOP字节码织入
在函数开始部分或结束部分嵌入字节码
可用于进行鉴权,日志等

public class Account { 
     public void operation() { 
         System.out.println("operation...."); 
     } 
}

在这个方法执行前鉴权或者添加日志

我们要嵌入的内容

public class SecurityChecker { 
    public static boolean checkSecurity() { 
        System.out.println("SecurityChecker.checkSecurity ...");
        return true;
    } 
}

ASM 实现

class AddSecurityCheckClassAdapter extends ClassVisitor {
    public AddSecurityCheckClassAdapter( ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }
    // 重写 visitMethod,访问到 "operation" 方法时,
    // 给出自定义 MethodVisitor,实际改写方法内容
    public MethodVisitor visitMethod(final int access, final String name, 
        final String desc, final String signature, final String[] exceptions) { 
        MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);
        MethodVisitor wrappedMv = mv; 
        if (mv != null) { 
            // 对于 "operation" 方法
            if (name.equals("operation")) { 
                // 使用自定义 MethodVisitor,实际改写方法内容
                wrappedMv = new AddSecurityCheckMethodAdapter(mv); 
            } 
        } 
        return wrappedMv; 
    } 
}
class AddSecurityCheckMethodAdapter extends MethodVisitor { 
     public AddSecurityCheckMethodAdapter(MethodVisitor mv) { 
         super(Opcodes.ASM5,mv); 
     } 
     public void visitCode() { 
         visitMethodInsn(Opcodes.INVOKESTATIC, "geym/jvm/ch10/asm/SecurityChecker", 
            "checkSecurity", "()Z"); 
         super.visitCode();
     } 
}
public class Generator{ 
     public static void main(String args[]) throws Exception { 
         ClassReader cr = new ClassReader("geym.jvm.ch10.asm.Account"); 
         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); 
         AddSecurityCheckClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw); 
         cr.accept(classAdapter, ClassReader.SKIP_DEBUG); 
         byte[] data = cw.toByteArray(); 
         File file = new File("bin/geym/jvm/ch10/asm/Account.class"); 
         FileOutputStream fout = new FileOutputStream(file); 
         fout.write(data); 
         fout.close(); 
     } 
}

输出结果

SecurityChecker.checkSecurity ...
operation....

JIT及其相关参数

字节码执行性能较差,所以可以对于热点代码编译成机器码再执行,在运行时的编译,叫做JIT Just-In-Time
JIT的基本思想是,将热点代码,就是执行比较频繁的代码,编译成机器码

JIT
当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为”Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机会把这些代码编译成与本地平台相关的机器码
JIT编译的基本思路

-XX:CompileThreshold=1000, 调用多少次的阈值设置
-XX:+PrintCompilation: 打出方法编译的信息

public class JITTest {

    public static void met(){
        int a=0,b=0;
        b=a+b;
    }

    public static void main(String[] args) {
        for(int i=0;i<1000;i++){
            met();
        }
    }
}

运行参数

-XX:CompileThreshold=1000 -XX:+PrintCompilation

运行输出:

56    1             java.lang.String::hashCode (55 bytes)
56    2             java.lang.String::equals (81 bytes)
57    3             java.lang.String::indexOf (70 bytes)
60    4             java.lang.String::charAt (29 bytes)
61    5             java.lang.String::length (6 bytes)
61    6             java.lang.String::lastIndexOf (52 bytes)
61    7             java.lang.String::toLowerCase (472 bytes)
67    8             geym.jvm.ch2.jit.JITTest::met (9 bytes)

可见,met方法被编译了

JIT的其余三个参
-Xint
  解释执行(不做编译,执行性能比较差)
-Xcomp
  全部编译执行(不管方法被调用的次数,所以方法都会被编译成机器码,在启动时性能会非常糟糕,在编异完成后,性能就会好很多)
-Xmixed
  默认,混合(解释执行和编译执行,取决于方法被调用的次数)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值