前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何找个对象(指令)

如何找个对象(指令)

作者头像
shysh95
发布2021-02-25 10:47:23
2660
发布2021-02-25 10:47:23
举报
文章被收录于专栏:shysh95

假期已经接近尾声了,新的一年废话不多说,直接开干,话说大家今年有没有领”对象“回家,祝有对象的情人节快乐,没有对象的没关系看完这篇文章就知道如何找个”对象“了,相约下一年和下一个情人节,今天主要讲几个指令类型:

  • 对象创建与访问指令
  • 操作数栈管理指令
  • 控制转移指令
  • 方法调用和返回指令
  • 异常处理指令
  • 同步指令

对象创建与访问指令

类实例和数组虽然都是对象,但JVM分别采用不同的指令进行创建,对象创建以后就可以通过访问指令进行访问。

  • 创建类实例:new
  • 创建数组实例:newarray、anewarray、multianewarray
  • 访问类字段(static字段)和实例字段(非static):getfiled、putfiled、getstatic、putstatic
  • 数组元素加载到操作数栈:baload、caload、salod、ialod、lalod、falod、dalod
  • 操作数栈元素加载到数组中:bastore、castore、sastore、iastore、lastore、fastore、dastore
  • 获取数组长度:arraylength
  • 检查类实例类型:instanceof、checkcast
代码语言:javascript
复制
public class ClassTest {

    public void checkCast(Object c) {
        System.out.println(c instanceof Integer);
        System.out.println((int)c);
    }
}

关于checkcast指令,如果发生类型强转不匹配,那么将会抛出ClassCastException。

操作数栈管理指令

JVM提供了一些可以直接操作操作数栈的指令:

  • 将栈顶的一个或者两个元素弹出:pop、pop2
  • 复制栈顶的一个或者两个元素并重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
  • 将操作栈顶的连个元素进行互换:swap
代码语言:javascript
复制
public class ClassTest {

    public static long pop2() {
       return 3L;
    }

    public static int pop() {
        return 1;
    }

    public static void main(String[] args) {
        pop();
        pop2();
    }
}

关于pop和pop2的区别主要是,pop弹出一个操作数栈中的一个32位的值,而pop2是弹出一个值,但是该值需要用两个32位来表示,或者弹出两个32位的值。因此由于long类型是64位,所以采用了pop2。

控制转移指令

控制转移指令可以让JVM有条件或者无条件的从指定位置执行程序。

  • 条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeg、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq、if_acmpne
  • 复合条件分支:tableswitch、lookupswitch
  • 无条件分支:goto、goto_w、jsr、jsr_w、ret

对于boolean、byte、char和short类型的条件分支比较操作,都采用int类型的比较指令来完成,而对于long、float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令(dcmpg、dcmpl、fcmpg、fcmpl、lcmp),比较运算指令会返回一个整型值到操作数栈中,然后再执行int类型的条件分支比较操作完成分支的跳转。

代码语言:javascript
复制
public class ClassTest {

    public void compare(long a) {
        if (a > 3) {
            System.out.println("1");
        } else {
            System.out.println("2");
        }
    }
}

我们解释一下compare方法的Code属性(这里我们假设a的值为1):

0:将第一个局部变量压入操作数栈

1:将常量池中第二项元素(3)压入操作数栈顶

2:这里就采用到了比较运算指令lcmp,lcmp会将栈顶的两个元素弹出,如果栈顶的元素大于第二个栈元素,那就将-1压入操作数栈中,如果栈顶额元素等于第二个栈元素,那么就将0压入操作数栈中,如果栈顶元素小于第二个栈元素,那么将1压入操作数栈中。由于我们这边a的值为1,因此会将-1压入操作数栈中

3:下面采用ifle条件分支比较指令,从下图中我们可以得知ifle会将栈顶元素与0比较,只有栈顶元素小于等于0是结果才为true,由于栈顶的元素是-1,因此结果为true,所以指令将会跳转到第19条执行。

19:getstatic是从class中获取一个static属性,这里是PrintStream 22:将常量池中的第7项元素也就是String的2压入操作数栈顶 23:invokevirtual是方法调用指令,用于调用对象的实例方法,这里就是打印2

方法调用和返回指令

  • invokevirtual:调用对象的实例方法
  • invokeinerface:调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找到合适的方法进行调用
  • invokespecial:调用一些特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法
  • invokestatic:调用类方法(静态方法)
  • invokedynamic:用于在运行时动态解析出调用点限定符引用的方法,并执行该方法
  • ireturn:方法返回指令,当返回值是boolean、byte、short、char、int时使用
  • lreturn:返回long类型
  • freturn:返回float类型
  • dreturn:返回double类型
  • areturn:返回reference类型
  • return:方法返回void时候使用

关于invokeddynamic指令下一篇文章中我会以lambda为切入点进行讲解,感兴趣的小伙伴可以关注一下公众号并置顶(防止错过)。

异常处理指令

Java程序中显示抛出异常(throw语句)都由athrow指令来实现。关于catch捕获的异常采用异常表来完成(不清楚的可以阅读一下JVM系列文章)。

同步指令

JVM支持方法级的同步和方法内部一段指令序列的同步,这两种都通过Monitor来实现。

方法级的同步是隐式的,在方法表上,如果一个方法被声明为ACC_SYNCHRONIZED,那么说明这个方法是个同步方法。当方法调用时,执行线程要求先成功持有Monitor,才能执行方法,方法执行完成后,不管是否正常结束都要释放Monitor。在方法执行期间,有且只有一个线程可以获得Monitor。

关于方法内部指令序列的同步通常使用synchronized关键字,JVM通过monitorenter和monitorexit两条指令来支持synchronized关键字。

synchronized关键字需要编译器和JVM两者的共同协作来支持。

编译器需要保证无论通过何种方式,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令。

代码语言:javascript
复制
public class ClassTest {

    private final Object lock = new Object();

    public void inc(int i) {
        synchronized (lock) {
            i = i + 1;
        }
    }
}

通过我们反编译的字节码序列可以看出,为了保证monitorenter和monitorexit配对执行,编译器自动生成了一个可以处理任何异常的异常处理表,这个异常处理表保证了monitorexit一定被执行。

本期JVM指令就介绍到这,我们下期再见!!!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员修炼笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档