假期已经接近尾声了,新的一年废话不多说,直接开干,话说大家今年有没有领”对象“回家,祝有对象的情人节快乐,没有对象的没关系看完这篇文章就知道如何找个”对象“了,相约下一年和下一个情人节,今天主要讲几个指令类型:
对象创建与访问指令
类实例和数组虽然都是对象,但JVM分别采用不同的指令进行创建,对象创建以后就可以通过访问指令进行访问。
public class ClassTest {
public void checkCast(Object c) {
System.out.println(c instanceof Integer);
System.out.println((int)c);
}
}
关于checkcast指令,如果发生类型强转不匹配,那么将会抛出ClassCastException。
操作数栈管理指令
JVM提供了一些可以直接操作操作数栈的指令:
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有条件或者无条件的从指定位置执行程序。
对于boolean、byte、char和short类型的条件分支比较操作,都采用int类型的比较指令来完成,而对于long、float、double类型的条件分支比较操作,则会先执行相应类型的比较运算指令(dcmpg、dcmpl、fcmpg、fcmpl、lcmp),比较运算指令会返回一个整型值到操作数栈中,然后再执行int类型的条件分支比较操作完成分支的跳转。
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
方法调用和返回指令
关于invokeddynamic指令下一篇文章中我会以lambda为切入点进行讲解,感兴趣的小伙伴可以关注一下公众号并置顶(防止错过)。
异常处理指令
Java程序中显示抛出异常(throw语句)都由athrow指令来实现。关于catch捕获的异常采用异常表来完成(不清楚的可以阅读一下JVM系列文章)。
同步指令
JVM支持方法级的同步和方法内部一段指令序列的同步,这两种都通过Monitor来实现。
方法级的同步是隐式的,在方法表上,如果一个方法被声明为ACC_SYNCHRONIZED,那么说明这个方法是个同步方法。当方法调用时,执行线程要求先成功持有Monitor,才能执行方法,方法执行完成后,不管是否正常结束都要释放Monitor。在方法执行期间,有且只有一个线程可以获得Monitor。
关于方法内部指令序列的同步通常使用synchronized关键字,JVM通过monitorenter和monitorexit两条指令来支持synchronized关键字。
synchronized关键字需要编译器和JVM两者的共同协作来支持。
编译器需要保证无论通过何种方式,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令。
public class ClassTest {
private final Object lock = new Object();
public void inc(int i) {
synchronized (lock) {
i = i + 1;
}
}
}
通过我们反编译的字节码序列可以看出,为了保证monitorenter和monitorexit配对执行,编译器自动生成了一个可以处理任何异常的异常处理表,这个异常处理表保证了monitorexit一定被执行。
本期JVM指令就介绍到这,我们下期再见!!!