为Java虚拟机编译

Java虚拟机是为了支持Java语音而设计的,Oracle的JDK包括两部分内容:一部分是将Java源代码编译成Java虚拟机的指令集的编译器,另一部分是用于Java虚拟机的运行时环境。

2.1 示例的格式说明

    本次的示例主要包括有源文件和Java虚拟机代码注解列表,其中Java虚拟机的代码注解列表是由Oracle的1.0.2版本的Jdk的javac编译器生成。Java虚拟机代码将使用Oracle的javap工具所生成的非正式的 虚拟机汇编语言的格式

格式如下:  <index> <opcode> [<operand1>  [<operand2>...]] [<comment>]

其中<index>是Code[]数组总的指令的操作码的索引,此处的Code数组就是存储当前方法的Java虚拟机字节码的Code属性中的Code数组。<opcode> 为指令的操作码的助记符号,<operandN> 是指令的操作数,一条指令可以有0--N个操作数。<commnet>为行尾的语法注释。譬如

8 bipush 100 //push int constant 100

注释中的某些部分是有javap自动加入。每条指令前的<index>可以作为控制转移指令的跳转目标,譬如 goto 8 指令表示跳转到索引为8的指令上面继续执行。

2.2 常量、局部变量的使用和控制结构

Java虚拟机是基于栈架构设计的,它的大多数操作是从当前栈帧的操作数栈取出一个或多个操作数,或将结果压入操作数栈。每调用一个方法,都会创建一个新的栈帧,并创建对应方法所需的操作数栈和局部变量表。每条线程在运行时的任意时刻,都会包含若干个由不同方法嵌套调用而产生的栈帧,只有当前栈帧中的操作数栈才是活动的。Java虚拟机指令集合使用不同的字节码来区分不同的操作类型 ,用于操作各种类型的变量。比如 iconst_0、、istore_1、iinc 这几个指令是以i 开头,意思是 操作int类型的数据类型。比如如下代码:  void test(){

       int i;

      for(i=0;i<100;i++){

     //循环操作为空

}

}

进入cmd,在该文件目录下,执行 javac Demo.java。生成了.class文件后,再执行 javap -c Demo

对应的代码:

        public void test();

            Code:

               0: iconst_0     //把int型 的值0压入操作数栈

               1:istore_1     //把操作数的栈 赋值给i,即将值保存在局部变量中

               2:iload_1  //把i的值取出

               3:bipush  100 //取值100

               5:if_icmpge          14     //把 0 和100进行比较,如果不满足,就 执行操作14,即 return.

               8:iinc    1,1                   //如果满足,i就自增1

               11:goto   2                 //自增1后 直接跳转到操作数2  iload_1 操作

               14: return                 //方法结束,返回

在Java虚拟机中,缺乏对byte、char和short类型数据直接操作的支持所带来的问题并不大,因为这些类型的值都在编译过程中就自动被转换为int类型,唯一额外的代价是将他们的长度扩展至4字节。

2.3算术运算

 Java虚拟机通常基于操作数栈来进行算术运算。算术运算使用到的操作数都是从操作数栈中弹出的,运算结果被压回操作数栈中。在内部运算的结果可以被当做操作数使用。

2.4访问运行时常量池

很多数值常量,以及对象,字段和方法,都是通过当前类的运行时常量池进行访问。类型为int、long、float和double的数据,以及表示String实例的引用类型数据的访问将由ldc、ldc_w和ldc2_w指令实现。其中 ldc和ldc_w指令用于访问运行时常量池中的对象,包括String实例,但不包括double和long类型。当使用的运行时常量池的项的数目过多时,多于256个,需要使用ldc_w指令取代ldc指令来访问常量池,ldc2_w指令用于访问类型为double和long类型的运行时常量池项。

参考示例:

  void demo(){

   int i=100;

  int j=1000000;

long l1=1;

long l2=0xffffffff;

double d=2.2;

}

编译后的代码(先 javac  ,然后javap -c ):

Method void demo()

0 bipush 100  //把int型 的值0压入操作数栈

2 istore_1  //把100 赋值给局部变量 i 

3 ldc #1  //把 1000000 压入操作数栈

5 istore_2 //把1000000赋值给j

6  lconst_1 //将long 类型的1压入操作数栈,值比较小的 采用了short的用法

7 lstore_3  //将long类型的1 赋值给l1

8 ldc2_w #6  //将一个大的 long型数据压入操作数栈

11 lstore 5  //将上面这个数赋值给l2

13 ldc2_w  //将2.2这个double类型的值压入操作数栈

 16 dstore 7  //赋值给d

对比java代码和编译后的代码(javap命令),理解了几个常用的赋值,取值 命令。基本也是很容易理解。

2.5 接收参数

 如果传递了n个参数给某个实例方法,则当前栈帧会按照约定的顺序接收这些参数,将他们保存为方法的第一个至第n个局部变量中。按照约定,实例方法需要传递一个自身实例的引用作为第0个局部变量。在Java语言中自身实例可以通过this关键字来访问。如果是静态方法,则不用传这个参数。

2.6方法调用

对普通实例方法调用是在运行时根据对象类型进行分派的,这类方法通过调用invokevirtual指令实现,每条invokevirtual指令都会带有一个表示索引的参数,运行时常量池在该索引处的项为某个方法的符号引用,这个符号引用可以提供方法所在对象的类型的内部二进制名称,方法名称和方法描述符。

2.7使用类实例

   Java虚拟机类实例通过Java虚拟机new指令创建,在Java虚拟机层面,构造函数将以一个编译器提供的以<init>命名的方法出现。这个特殊的名字的方法也被称作实例初始化方法。一个类可以有多个构造函数,对应的也就会有多个实例初始化方法。一旦类实例被创建,那么这个实例包含的所有实例变量,除了在本身定义的以及父类中所定义的,都将赋予初始值。

2.8 其他类型

其他的类型 还包括 数组,switch语句,抛出异常和使用异常等。具体的详情可以参考java虚拟机规范。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值