深入JVM学习笔记 JVM指令介绍

JVM指令介绍

栈和局部变量操作

1、常量入栈操作
--入栈操作三种方式指明常量的值:常量值隐式包含在操作码内部,常量值在字节码流中如同操作数一样紧随在操作码之后,或者从常量池中取出常量

--Java栈中每一个位置的长度都是一个字长(至少32位宽)

--Java源代码中所有的字符串文字最终都作为入口存储与常量池中。如果同一个应用程序的多个类都使用同样的字符串文字,按摩此字符串文字将在使用它的所有类的class文件中出现。但Java虚拟机会把所有具有相同字符顺序的字符串文字处理为同一个String对象,只会创建一个相应的String对象来表示所有的字符串文字。
备注:要深刻理解源代码中字符串文字与JVM生成的String对象的差别。

--wide指令:跳转指令并不允许直接跳转到被wide指令修改过的操作码。

类型转换

1、设计 byte short和char类型的运算操作首先会把这些值转换为int类型,然后对int类型值进行运算,最后得到int类型的结果。

整数运算

--取余运算 irem 取反运算 ineg

逻辑运算
-- "<<" ishl 向左移位
">>" ishr 向右移位
">>>" iushr 逻辑移位

浮点运算

1、在Java虚拟机中,浮点运算基于32位float类型和64位double类型进行。浮点数由符号、尾数、基数和指数四部分组成。

2、float类型的格式如下,符号位表示为s,指数位表示为e,尾数位表示为m:指数占8位,double类型中占11位
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
指数位的解释有三种方式:
指数位全为1,表示该数为乘法或者减法所产生的特殊值之一——无穷大或者非数字(NaN),NaN是某种特殊操作,诸如0除以0的结果。
指数位全是0,表示该数是一个非规范化浮点数。
其他类型的指数位表示该数十一个规范化的浮点数。

3、float类型的特殊值
指数位全为1而且尾数位全为0,表示无穷大
指数位全为1,尾数位不全为0,表示该数为"NaN":Java虚拟机总是为NaN产生同样的尾数,除了尾数中最高有效位为1外,其余全为0

4、2的幂指数的确定
指数位既不全为0,也不全为1,该数即为规范化的浮点数。可以通过把指数看成一个整数,然后减去一个偏移量来确定2的幂指数。对于float类型,偏移量是127,double类型是1023

浮点数表示参考文章:http://apps.hi.baidu.com/share/detail/30212277

对象和数组

1、只有对象引用和基本类型可以在Java的栈中以局部变量形式存在,Java栈中不能容纳对象。

2、针对数组的操作码
--newarray操作码用来创建基本类型的数组
--anewarray/multianewarray操作码用来创建对象引用的数组

finally子句
--jsr指令是使Java虚拟机跳转到微型子例程的操作码。
--ret指令的功能是执行从子例程中返回的操作。
jsr指令会把返回地址压入栈,在执行子例程开始时又会把返回地址弹出栈,然后存入局部变量中。当finally执行结束后(此结束是指finally执行到了最后一条语句正常执行完毕,不包括抛出异常,或执行return、continue、break等情况),ret指令从局部变量里取出返回地址,然后返回。如果finally子句非正常结束,则根本不会执行ret指令,这也是为什么返回地址要从栈中弹出并存到局部变量的原因,否则如果ret指令不执行该地址将存在在栈中而不会被用到。

--Java虚拟机在finally子句执行完毕钱返回一个值的方式:
public class Test4 {

public int test(){
int i = 0;
try{
i = 1;
return i;
}finally{
i = 2;
}
}
public static void main(String[] args) {
Test4 t = new Test4();
System.out.println(t.test());
}
}
---------------------
i = 1


虽然finally改变了i的值,但是虚拟机仍会返回执行finally子句前的i的值;如果需要finally子句改变返回值,就不得不在finally子句里加一个return语句,用来返回被finally子句更新过的值(如下代码)。

public class Test4 {

public int test(){
int i = 0;
try{
i = 1;
}finally{
i = 2;
return i;
}
}
public static void main(String[] args) {
Test4 t = new Test4();
System.out.println(t.test());
}
}
---------------------
i = 2


方法的调用和返回

1、对于实例方法,使用invokevirtual指令;对于类方法,使用invokestatic指令。
[img]http://dl.iteye.com/upload/attachment/590973/41d97932-b811-356e-b381-fb94ec979e0e.png[/img]

2、方法调用的过程描述
[img]http://dl.iteye.com/upload/attachment/590975/cc5a45ba-b579-39f1-a3f6-aee31f336914.png[/img]

3、通常使用invokevirtual指令调用实例方法,但在某些特定的情况下,也会使用另外两种操作码——invokespecial和invokeinterface。
--当根据引用的类型来调用实例方法,而不是根据对象的类来调用的时候,通常使用invokespecial指令。分三种情况:
1)实例初始化(<init>())方法
2)私有方法
3)使用super关键字所调用的方法
--当给出一个接口的引用时,使用invokeinterface来调用一个实例方法

备注:invokevirtual只能调用当前类的方法,无法使用超类的方法。——对象初始化时在调用自身的<init>()的时候会首先调用父类的<init>()方法,而invokespecial可以完成这样的操作。

释疑: 什么是根据引用的类型,什么是根据对象的类(参考下面的代码可知)
情况一:
public class SuperClass{
private void speakHi(){
System.out.println("super class say hi");
}

void sayHi(){
speakHi();
}
}

public class SubClass extends SuperClass{
void speakHi(){
System.out.println("sub class say hi");
}

public static void main(String[] args){
SubClass ins = new SubClas();
ins.sayHi();
}
}

运行结果: super class say hi
解释:main方法中创建的对象类型是SubClass,但SubClass的sayHi()方法是从SuperClass继承过来的,所以此时引用的类型其实是SuperClass,而invokespecial根据引用的类型来调用实例方法,即SuperClass的speakHi()。且此时所调用方法为私有方法。

情况二:
 public class SuperClass{
private void speakHi(){
System.out.println("super class say hi");
}

void sayHi(){
speakHi();
}
}

public class SubClass extends SuperClass{
void speakHi(){
System.out.println("sub class say hi");
}

void sayHi(){
speakHi();
}

public static void main(String[] args){
SubClass ins = new SubClas();
ins.sayHi();
}
}

运行结果: sub class say hi
解释:SubClass覆盖了SuperClass的sayHi()方法,此时不仅创建的对象类型是SubClass,真正引用的类型也是,所以调用的就是SubClass的speakHi()。这也从另一面证明了第一种情况的正确性。

--Java虚拟机使用不同于类引用(invokevirtual)的操作码来调用接口引用(invokeinterface)的方法,这是因为Java不能像使用类引用那样,使用许多和方法表偏移量相关的假设。对于类引用来说,无论对象实际的类是什么,方法在方法表中始终占据相同的位置,但对于接口引用来说,情况就不是这样了,位于不同类中的同一方法所占据的位置是不同的,尽管这些类实现同一个接口。

线程同步

1、Java使用的同步机制是监视器。
--一个线程只有在它正持有监视器时才能执行等待命令,而且它只能通过再次成为监视器的持有者才能离开等待区。

2、对象锁
--堆和方法区是被所有线程共享的,Java程序需要为两种多线程访问数据进行协调:
保存在堆中的实例变量
保存在方法区中的类变量

3、可重入锁:一个线程可以允许多次对同一个对象上锁。对于每一个对象来说,Java虚拟机维护一个计数器,记录对象被加了多少次锁。没有被锁的对象的计数器是0,当一个线程第一次获得锁的时候计数跳到1,线程没加锁一次,计数器就加1,(只有已经拥有了这个对象的锁的线程才能对该对象再次加锁。在它释放锁之前,其他的线程不能对这个对象加锁)每当线程释放锁一次,计数器就减1,当计数器跳到0的时候,锁就完全被释放了,其他的线程才可以使用它。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值