看一个简单的例子,有几个类变量,类初始化时调用类方法,其中还包括简单加法运算,字符串重载以及输出。

Clac.java //java源码

public class Calc {

private static int numberA = 10;

private static int numberB = (int) (Math.random() * 7.7);

private static String show = "the result is ";

public static void main(String[] args) {

numberA = 20;

int result = numberA + numberB;

                show = show + result;

System.out.println(show);

}

}

 Calc.class文件结构 //主要信息,并不是全部

D:\Dev>javap -c -s -l -verbose Calc

Constant pool(常量池):

const #1 = Method       #17.#32;        //  java/lang/Object."<init>":()V

const #2 = Field        #16.#33;        //  Calc.numberA:I

const #3 = Field        #16.#34;        //  Calc.numberB:I

const #4 = class        #35;    //  java/lang/StringBuilder

const #5 = Method       #4.#32; //  java/lang/StringBuilder."<init>":()V

const #6 = Field        #16.#36;        //  Calc.show:Ljava/lang/String;

const #7 = Method       #4.#37; //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

const #8 = Method       #4.#38; //  java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

const #9 = Method       #4.#39; //  java/lang/StringBuilder.toString:()Ljava/lang/String;

const #10 = Field       #40.#41;        //  java/lang/System.out:Ljava/io/PrintStream;

const #11 = Method      #42.#43;        //  java/io/PrintStream.println:(Ljava/lang/String;)V

const #12 = Method      #44.#45;        //  java/lang/Math.random:()D

const #13 = double      7.7d;

const #15 = String      #46;    //  the result is

const #16 = class       #47;    //  Calc

const #17 = class       #48;    //  java/lang/Object

const #18 = Asciz       numberA;

const #19 = Asciz       I;

const #20 = Asciz       numberB;

const #21 = Asciz       show;

const #22 = Asciz       Ljava/lang/String;;

const #23 = Asciz       <init>;

const #24 = Asciz       ()V;

const #25 = Asciz       Code;

const #26 = Asciz       LineNumberTable;

const #27 = Asciz       main;

const #28 = Asciz       ([Ljava/lang/String;)V;

const #29 = Asciz       <clinit>;

const #30 = Asciz       SourceFile;

const #31 = Asciz       Calc.java;

const #32 = NameAndType #23:#24;//  "<init>":()V

const #33 = NameAndType #18:#19;//  numberA:I

const #34 = NameAndType #20:#19;//  numberB:I

const #35 = Asciz       java/lang/StringBuilder;

const #36 = NameAndType #21:#22;//  show:Ljava/lang/String;

const #37 = NameAndType #49:#50;//  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

const #38 = NameAndType #49:#51;//  append:(I)Ljava/lang/StringBuilder;

const #39 = NameAndType #52:#53;//  toString:()Ljava/lang/String;

const #40 = class       #54;    //  java/lang/System

const #41 = NameAndType #55:#56;//  out:Ljava/io/PrintStream;

const #42 = class       #57;    //  java/io/PrintStream

const #43 = NameAndType #58:#59;//  println:(Ljava/lang/String;)V

const #44 = class       #60;    //  java/lang/Math

const #45 = NameAndType #61:#62;//  random:()D

const #46 = Asciz       the result is ;

const #47 = Asciz       Calc;

const #48 = Asciz       java/lang/Object;

const #49 = Asciz       append;

const #50 = Asciz       (Ljava/lang/String;)Ljava/lang/StringBuilder;;

const #51 = Asciz       (I)Ljava/lang/StringBuilder;;

const #52 = Asciz       toString;

const #53 = Asciz       ()Ljava/lang/String;;

const #54 = Asciz       java/lang/System;

const #55 = Asciz       out;

const #56 = Asciz       Ljava/io/PrintStream;;

const #57 = Asciz       java/io/PrintStream;

const #58 = Asciz       println;

const #59 = Asciz       (Ljava/lang/String;)V;

const #60 = Asciz       java/lang/Math;

const #61 = Asciz       random;

const #62 = Asciz       ()D;

{

public Calc();

  Signature: ()V

   0:   aload_0

   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V

   4:   return

public static void main(java.lang.String[]);

  Signature: ([Ljava/lang/String;)V

   0:   bipush  20

   2:   putstatic       #2; //Field numberA:I

   5:   getstatic       #2; //Field numberA:I

   8:   getstatic       #3; //Field numberB:I

   11:  iadd

   12:  istore_1

   13:  new     #4; //class java/lang/StringBuilder 创建一个对象,并将其引用值压入栈顶

   16:  dup            //复制栈顶数一个字长的值并将复制值压入栈顶

   17:  invokespecial   #5; //Method java/lang/StringBuilder."<init>":()V 

   20:  getstatic       #6; //Field show:Ljava/lang/String;

   23:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;append方法返回一个对象

   26:  iload_1

   27:  invokevirtual   #8; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;

   30:  invokevirtual   #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

   33:  putstatic       #6; //Field show:Ljava/lang/String;

   36:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;

   39:  getstatic       #6; //Field show:Ljava/lang/String;

   42:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V

   45:  return

static {};

  Signature: ()V

  Code:

   Stack=4, Locals=0, Args_size=0

   0:   bipush  10

   2:   putstatic       #2; //Field numberA:I

   5:   invokestatic    #12; //Method java/lang/Math.random:()D

   8:   ldc2_w  #13; //double 7.7d

   11:  dmul

   12:  d2i

   13:  putstatic       #3; //Field numberB:I

   16:  ldc     #15;    //String the result is

   18:  putstatic       #6; //Field show:Ljava/lang/String;

   21:  return

}

* 关于常量池 

在class文件结构中常量池占了很大一部份,由很多个表组成,每一项就是一个表,关于表的结构可以查看一下《深入Java虚拟机第二版》的第六章,里边有祥细的解释。关于常量池,正如其名称,存储的都是常量,那常量都是哪些呢? 根据《深入Java虚拟机第二版》的介绍,常量包括直接常和对其它类型,字段以及方法的引用。对照上面的class的文件,源码的变量numberA,在常量池对应的是Calc.numberA:I,这个是符号引用,入口地址为#2(常量池的偏移地址),show变量的初始化的值也是存储在常量池中,从常量池入口地址#15可以得到,解析过程后面会讲到。

 

* JVM第一步 装载

首在当然把这个Class文件装载到虚拟中,在方法区为之分配一块内存,这个过程有三个基本动作:

1)通过该类的完全限定名,产生一个代表该类型的二进制流。

2)解析这个二进制数据流为方法区内总数据结构。

3)创建一个代表该类型的java.lang.Class实例

产生二进制数据流的方式主要有七种,class文件可由启动类型装载器或者用户自定义的类装载器装载,这方面就涉及到ClassLoader编程。

装载的最终产品是Class实例对象,它成为Java程序与内部数据结构之间的接口,要访问类型信息都需要通过Class实例对象,比如说我们新一个对象的时候

* JVM第二步 验证

这个过程发生各个阶段上都会有,只是验证的内容不一样。

1)第一趟:class文件结构的检查

2)类型数据的语义检查

3)字节码验证

4)符号引用的验证 (可能发生在初始化前或者后)

* JVM第三步 准备

当前面的步骤都顺利完成,准备阶段就可以开始,Java为类变量为分配内存并设置其默认值,但并不会为实例变量分配内存,这个在创建对象时才分配

* JVM第四步 解析(可选)

根据JVM不同的实现,解析分为两种,早解析与晚解析,就是发生在初始化前后。解析过程就是把符号引用变成直接引用。比如说前面的例子,当调用System.out变量时,它的符跟引用是 java/lang/System.out,在晚解析的情况下,JVM这时才装载,连接,初始化java/lang/System这个类型,然后再把这个符号引用替换成直接引用。

*** 二,三,四步也统称为连接

* JVM第五步 初始化 

初始化过程就是执行 <clinit>()方法,但需要注意的执行顺序,因为有超类的影响。

 

下面就看前面那个class文件的字节码运行过程,

 * 执行static {};(<cinit>()方法),

bipush  10 //将单字节的常量值10推送至栈顶  

putstatic   #2 //把栈顶的值送给类变量。我们看看常量池入口#2,指向的是字段表,里边两个常量池入口,顺着这些常量池入口就可以得到当前类名,字段名以及类型,符号引用(Calc.numberA:I)就找着了,前面提到在准备阶段已经为类变量分配了一块内存,现在就在常量池入口#2中放入numberA的直接引用并标记已解析,下一次访问这个变量时就可以直接读取

invokestatic    #12 //调用静态方法,把方法返回值压入栈顶。JVM找到常量池入口#12,可以得到符号引用(java/lang/Math.random:()D),Java虚拟机要解析这个符号引用,这个将导致java/lang/Math类的装载,连接与初始化,然后在常量池下口#12放入一个指向random()方法的直接引用,把这个入口标记为已解析,并且把invokestatic变成invokestatic_quick.

ldc2_w  #13  //double型常量值从常量池中推送至栈顶,取两个字。虚拟机找到常量池入口#13,发现并未解析,虚拟机把这入口解析7.7d,标记已解析并且把ldc2_w变成ldc2_w_quick.有意思的是没有常量池入口#14,这是因为double型占用两个位置,一个位置相当于一个字长。

dmul //将栈顶两double型数值相乘并将结果压入栈顶

d2i //将栈顶double型数值强制转换成int型数值并将结果压入栈顶  

putstatic    #3 // 把栈顶的值送给类变量

ldc     #15 // 虚拟机找到常量池入口#15,指向一个String,值在常量池入口#46(the result is),字符串解析并不是那么简单.虚拟机都在会在内部维护一张表,记录在程序运行过程中已被拘留的字符串对象引用.因此虚拟机在解析字符串的时候,首先会在这张内部表查找否是有了这个字符串引用,如果已存在,则把这个拘留字符串对象引用放入常池入口;如果没有,则在堆重创建一个字符串对象,并把它的引用添加到内部表,再把常量池入口指向这个引用。 

putstatic       #6; //把栈顶的值送给类变量,并标记已解析

类型初始化已经结束.

* main方法的解析

main方法前六条指令比较通俗易懂,就是把常值赋给变量numberA,接下来再取变量numberA与numberB的值,求和并结果赋给局部变量result.

接下来的指令就比较有意思了,首先new一个StringBuilder对象,把引用放入栈顶,接着就dup(在栈顶复制一个字长的值),复制一个对象引用,也放入栈顶,为何要复制一份呢?先看下一条指令,invokespecial #5,调用StringBuilder的对象初始化方法,我们都知道,在调用一个非静态方法时,都必需传一个对象引用作为参数,这就是为什么Java关键字this只能在非静态方法使用,这也说明了在使用一个对象要对其初始化,因此复制的引用就是传递给对象初始化方法。***注意现在栈中只剩下一个对象引用***,可后面还调用了StringBuilder的三个非静态方法,而且没有变量保存StringBuilder引用,一个对象引用怎么可以给三个非静态方法调用,这可怎么办啊?而神奇的地方就在于append方法会返回一个对象引用,为下一个方法准备好参数,不过情况只能发生在StringBuilder为匿名对象,如果是一般对象(StringBuilder s = new StringBuilder()),在调用完append方法之后会有一个pop操作,目的就是清除append返回的对象引用。而在上面的字节码,第一个append方法返回的对象引用作为第二个append方法的参数,第二个append返回的对象引用刚好是作为toString的参数,感觉很顺畅,这应该是(String sa = sb + sc)这种操作的固定模式,对于多个重载操作,速度应该很可观。
 后面的指令也比较简单,get变量值,调用println方法输出,最后调用return结束。

posted on 2010-08-25 19:59  ALVINZ  阅读(441)  评论(0编辑  收藏  举报