`
Irving_wei
  • 浏览: 130337 次
  • 性别: Icon_minigender_1
  • 来自: Heaven
社区版块
存档分类
最新评论

JVM指令

    博客分类:
  • JVM
阅读更多
转自:http://blog.csdn.net/chenzhp/archive/2007/09/24/1798166.aspx

源代码经过编译器编译之后便会生成一个字节码文件,字节码是一种二进制的类文件,它的内容是JVM的指令,而不像C、C++经由编译器直接生成机器码。我们不用担心生成的字节码文件的兼容性,因为所有的JVM全部遵守Java虚拟机规范,也就是说所有的JVM环境都是一样的,这样一来字节码文件可以在各种JVM上运行。 当然也包括KVM。

每一个线程都有一个保存帧的栈。在每一个方法调用的时候创建一个帧。一个帧包括了三个部分:操作栈,局部变量数组,和一个对当前方法所属类的常量池的引用。
局部变量数组也被称之为局部变量表,它包含了方法的参数,也用于保存一些局部变量的值。参数值得存放总是在局部变量数组的index0开始的。如果当前帧是由构造函数或者实例方法创建的,那么该对象引用将会存放在location0处,然后才开始存放其余的参数。
局部变量表的大小由编译时决定,同时也依赖于局部变量的数量和一些方法的大小。操作栈是一个(LIFO)栈,用于压入和取出值,其大小也在编译时决定。某些opcode指令将值压入操作栈,其余的opcode指令将操作数取出栈。使用它们后再把结果压入栈。操作栈也用于接收从方法中返回的值。
以HelloWorld程序为例,经过命令:
E:\JavaExe>javap -c HelloWorld>HelloWorld.bytecode
就会在目录下生成一个字节码文件,用编辑器打开后

Compiled from "HelloWorld.java"
class HelloWorld extends java.lang.Object{
public HelloWorld(java.lang.String,int);
  Code:
   0:      aload_0
   1:      invokespecial #1; //Method java/lang/Object."<init>":()V
   4:      aload_0
   5:      ldc   #2; //String
   7:      putfield   #3; //Field name:Ljava/lang/String;
   10:    aload_0
   11:    iconst_0
   12:    putfield   #4; //Field idNumber:I
   15:    aload_0
   16:    aload_1
   17:    putfield   #3; //Field name:Ljava/lang/String;
   20:    aload_0
   21:    iload_2
   22:    putfield   #4; //Field idNumber:I
   25:    aload_0
   26:    aload_1
   27:    iload_2
   28:    invokevirtual  #5; //Method StoreData:(Ljava/lang/String;I)V
   31:    return

public void StoreData(java.lang.String,int);
  Code:
   0:      bipush    90
   2:      istore_2
   3:      return

void print(AnotherClass);
  Code:
   0:      aload_1
   1:      bipush    10
   3:      putfield   #6; //Field AnotherClass.a:I
   6:      new #7; //class AnotherClass
   9:      dup
   10:    invokespecial #8; //Method AnotherClass."<init>":()V
   13:    astore_1
   14:    aload_1
   15:    bipush    20
   17:    putfield   #6; //Field AnotherClass.a:I
   20:    return

}

以上是经过编译后的HelloWorld的字节码文件。我们可以对照源文件来查看一些重要的指令。
class HelloWorld
{
     private   String name = "";
     private  int idNumber = 0;
     public HelloWorld(String strName, int num)
     {
      
        name = strName;
        idNumber = num;
        StoreData(strName,num);
     }
  public  void StoreData(String str,int i)
  {
        i = 90;
  }
  void print(AnotherClass another)
  {
         another.a=10;
         another=new AnotherClass();
         another.a=20;
   }

}
class AnotherClass
{
    public int a = 0;
   
}
void print(AnotherClass);
  Code:
   0:      aload_1
   1:      bipush    10
   3:      putfield   #6; //Field AnotherClass.a:I
   6:      new #7; //class AnotherClass
   9:      dup
   10:    invokespecial #8; //Method AnotherClass."<init>":()V
   13:    astore_1
   14:    aload_1
   15:    bipush    20
   17:    putfield   #6; //Field AnotherClass.a:I
   20:    return

aload_1 把存放在局部变量表中索引1位置的对象引用压入操作栈
bipush 10 把整数10压入栈
putfield #2 把成员变量a的值设置成栈中的10,#2代表2号常量项
new #3 创建AnotherClass的对象,把引用放入栈
dup 复制刚放入的引用(这时存在着两个相同的引用)
invokespecial #4 通过其中的一个引用调用AnotherClass的构造器,初始化对象,让另一个相同引用指向初始化的对象,然后前一个引用(this)弹出栈
asstore_1 把引用保存到局部变量表中的索引1位置中,然后引用弹出栈
aload_1 把局部变量表中索引1处的值压入操作栈。
bipush 20 把整数20压入栈
putfield #2 把成员变量a的值设置成栈中的10
return 执行完毕退出

我们继续看构造函数中的一段代码:
public HelloWorld(java.lang.String,int);
  Code:
将该(this)对象压入操作栈,对于实例方法和构造函数的局部变量表来说第一个入口总是这个“this”。因为你需要访问一些实例中的方法和变量。
   0:      aload_0 
   调用该类的超类构造函数,因为所有类都继承与Java.lang.Object。而该类(HelloWorld)没有new函数操作,所以编译器提供必要的字节码来调用这些基类构造器。
   1:      invokespecial #1; //Method java/lang/Object."<init>":()V
   将该(this)对象压入操作栈
4:    aload_0
字符串
    5:    ldc   #2; //String abc
把栈中的name的值置为栈中的”abc”
   7:      putfield   #3; //Field name:Ljava/lang/String;
   同样,将this压入栈
   10:    aload_0
   将0压入栈。
   11:    iconst_0
   将idNumber置为栈中的0,就是上一句指令中的操作
   12:    putfield   #4; //Field idNumber:I
   将this压入栈
   15:    aload_0
   将位于局部变量表中位置1处的方法的形参strName压入栈
   16:    aload_1
   将name的值置为栈中的strName
   17:    putfield   #3; //Field name:Ljava/lang/String;
   将this压入栈,this总是位于局部变量表的index0处!
   20:    aload_0
   将位于局部变量表中位置2处的方法形参num压入栈
   21:    iload_2
   同17号操作,赋值
   22:    putfield   #4; //Field idNumber:I
   将this压入操作栈
   25:    aload_0
   将strName压入栈
   26:    aload_1
将num压入栈
   27:    iload_2
   调用方法StoreData
   28:    invokevirtual  #5; //Method StoreData:(Ljava/lang/String;I)V
   31:    return
  如果有()V 标志方法没有参数列表
我们观察发现,在每一个opcode指令的左边的位置序号都不是连续的。0,1,4,5,7,10……为什么?
每一个方法都有一个对应得ByteCode序列,这些值对应着每一个opcode和其参数存放的序列中的某一个索引值。为什么这些索引不是顺序的?既然每一个指令占据一个字节,那索引为什么不是0,1,2呢?原因是:一些指令的参数占据了一些bytecode数组空间。比如:
Aload_0指令没有参数,所以占有一个字节,第二个指令invokespecial,由于它本身带有参数,结果它本身和参数分别就占据了一个位置,所以,上面的1过了就不是4。

Aload_0
invokespecial
00
05
return

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics