论坛首页 Java企业应用论坛

java指令详解

浏览 15249 次
精华帖 (3) :: 良好帖 (4) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-10-09   最后修改:2011-02-28

class文件格式如下:

相信学java的人都对new  Object();创建对象都很熟悉,但想要真正了解原理就没那么容易!以以下例子为例,解释class代码及执行过程,如有错误,还望各位高手多多指教!


public class Dog {
	public String name;
	public int age;
	public Dog() {
	}
	public Dog(String name)
	{
		this.name = name;
	}
	public Dog(String name, int age)
	{
		this.name = name;
		this.age = age;
	}
	public static void  getStaticValue(int j)
	{
		int i=j;
		System.out.println(i);
	}
	public  void  getValue(int j)
	{
		int i=j;
		System.out.println(i);
	}
	public static void main(String[] args) {
		try {
			new Dog().getValue(10);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
} 

如上代码很简单,main方法加上try catch只是为了  让大家看一下java文件生产字节码是怎么样的

执行javac  Dog.java文件,生成class文件。然后使用javap -verbose Dog反编译出class代码。

生成如下代码:

view plaincopy to clipboardprint?
Compiled from "Dog.java"  
public class Dog extends java.lang.Object   
  SourceFile: "Dog.java"  
  minor version: 0   // minor version: major version:这两个是表示class文件的版本号,                            
  major version: 50   //详细见http://blog.csdn.net/xiaxiaorui2003/archive/2009/07/07/4327029.aspx这位兄弟的blog   
  
  
  Constant pool:      //常量池,如下以const开头的都是常量池信息,每个class文件都有一些常量池信息   
                     //当线程调用一个方法的时候,jvm会开辟一个帧出来,这个帧包括操作栈、局部变量列表、常量池的引用   
                     //如下以#开头的表示偏移量编号,相当于id号,接下来解释如下代码的作用   
  
  
如下 #11.#31; 这段代码什么作用呢?很简单,就是表示创建Object对象。   
首先来看.号左边的#11,找到常量池中#11,跳到const #11 = class       #42;    //  java/lang/Object   
这个表示是一个Object类型,class后面的 #42;表示接下来要跳到常量池中#42  
找到const #42 = Asciz       java/lang/Object;  表示生成Object类型   
接下来看.号右边的#31。   
const #31 = NameAndType #16:#17;     //解释:这里指定到#16 #17   
const #16 = Asciz       <init>;   
const #17 = Asciz       ()V;      //解释:到这里完成初始化工作   
  
  
  
//NameAndType表示名字和类型   调用构造方法 的Name都是<init>,   V表示没有返回值     ,()  括号里面是空的表示没有参数   
//到这里完成Dog   
//   
如下有  :   
Method   //方法   
Field   //类名.属性   
class   //类型   
Asciz   //方法签名   
NameAndType   //变量名和类型   
  
java类型对应的class文件方法签名的标识符:   
invokespecial  //调用构造方法、父类方法   
invokevirtual  //调用普通方法(非构造方法、static方法)   
invokestatic   //调用static方法   
  
Ljava/lang/String;;  //这表示String类型,这里要全路径,java/lang/String;;前面的L表示非java八大基本类型   
void V   
int char byte short long float double 都是类型第一个字母(大写)   
boolean 比较特别,用Z表示  ,因为B被byte给占用了   
  
  
const #1 = Method       #11.#31;        //  java/lang/Object."<init>":()V     解释:初始化Object   
  
const #2 = Field        #6.#32; //  Dog.name:Ljava/lang/String;      Dog类中定义的String name   
const #3 = Field        #6.#33; //  Dog.age:I        Dog类中定义的int age   
const #4 = Field        #34.#35;        //  java/lang/System.out:Ljava/io/PrintStream;   
const #5 = Method       #36.#37;        //  java/io/PrintStream.println:(I)V      解释:调用println(int value), V表示返回值是void   
const #6 = class        #38;    //  Dog   
const #7 = Method       #6.#31; //  Dog."<init>":()V     解释:初始化Dog,调用构造函数,"<init>"是初始化标识符, V表示返回值是void   
const #8 = Method       #6.#39; //  Dog.getValue:(I)V      解释:调用getValue(int j)方法, V表示返回值是void   
const #9 = class        #40;    //  java/lang/Exception   
const #10 = Method      #9.#41; //  java/lang/Exception.printStackTrace:()V   
const #11 = class       #42;    //  java/lang/Object   
const #12 = Asciz       name;   
const #13 = Asciz       Ljava/lang/String;;   
const #14 = Asciz       age;   
const #15 = Asciz       I;   
const #16 = Asciz       <init>;   
const #17 = Asciz       ()V;   
const #18 = Asciz       Code;   
const #19 = Asciz       LineNumberTable;   
const #20 = Asciz       (Ljava/lang/String;)V;   
const #21 = Asciz       (Ljava/lang/String;I)V;   
const #22 = Asciz       getStaticValue;   
const #23 = Asciz       (I)V;   
const #24 = Asciz       getValue;   
const #25 = Asciz       main;   
const #26 = Asciz       ([Ljava/lang/String;)V;   
const #27 = Asciz       StackMapTable;   
const #28 = class       #40;    //  java/lang/Exception   
const #29 = Asciz       SourceFile;   
const #30 = Asciz       Dog.java;   
const #31 = NameAndType #16:#17;//  "<init>":()V   
const #32 = NameAndType #12:#13;//  name:Ljava/lang/String;   
const #33 = NameAndType #14:#15;//  age:I   
const #34 = class       #43;    //  java/lang/System   
const #35 = NameAndType #44:#45;//  out:Ljava/io/PrintStream;   
const #36 = class       #46;    //  java/io/PrintStream   
const #37 = NameAndType #47:#23;//  println:(I)V   
const #38 = Asciz       Dog;   
const #39 = NameAndType #24:#23;//  getValue:(I)V   
const #40 = Asciz       java/lang/Exception;   
const #41 = NameAndType #48:#17;//  printStackTrace:()V   
const #42 = Asciz       java/lang/Object;   
const #43 = Asciz       java/lang/System;   
const #44 = Asciz       out;   
const #45 = Asciz       Ljava/io/PrintStream;;   
const #46 = Asciz       java/io/PrintStream;   
const #47 = Asciz       println;   
const #48 = Asciz       printStackTrace;   
  
{   
public java.lang.String name;   
  
public int age;   
  
  
  
  
//如下的Locals表示方法内局部变量个数,该例中是1,有些人疑惑的是Dog()中明明没有参数啊,应该是0啊!   
//当线程调用一个方法的时候,jvm会开辟一个帧出来,这个帧包括操作栈、局部变量列表、常量池的引用   
//非static方法,在调用的时候都会给方法默认加上一个当前对象(this)类型的参数,不需要在方法中定义,   
//这个时候局部变量列表中index为0的位置保存的是this,其他索引号按变量定义顺序累加   
//static方法不依赖对象,所以不用传this   
//Args_size表示参数个数,public Dog();会传一个this进去,所以value是1   
public Dog();   
  Code:   
   Stack=1, Locals=1, Args_size=1       
   0:   aload_0    //加载局部变量表index为0的变量,在这里是this   
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V      //调用构造方法   
   4:   return  
  LineNumberTable:   
   line 6: 0  
   line 7: 4  
  
  
  
//这个构造方法与上个构造方法也是同理,只是多少个String参数和  给name赋值   
public Dog(java.lang.String);   
  Code:   
   Stack=2, Locals=2, Args_size=2  
   0:   aload_0   
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V        
                    //这里的#1;表示对常量池的引用,创建一个类,必须先初始化父类,创建Dog之前创建Object   
   4:   aload_0     //加载局部变量表index为0的变量,在这里是this   
   5:   aload_1     //加载局部变量表index为1的变量,在这里是String name局部变量   
   6:   putfield        #2; //Field name:Ljava/lang/String;   赋值操作   
   9:   return  
  LineNumberTable:   
   line 10: 0  
   line 11: 4  
   line 12: 9  
  
  
public Dog(java.lang.String, int);   
  Code:   
   Stack=2, Locals=3, Args_size=3  
   0:   aload_0   
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V   
   4:   aload_0   
   5:   aload_1   
   6:   putfield        #2; //Field name:Ljava/lang/String;   
   9:   aload_0   
   10:  iload_2   
   11:  putfield        #3; //Field age:I   
   14:  return  
  LineNumberTable:   
   line 15: 0  
   line 16: 4  
   line 17: 9  
   line 18: 14  
  
//这里的Args_size=1,是因为是static方法,不会传进this   
public static void getStaticValue(int);   
  Code:   
   Stack=2, Locals=2, Args_size=1  
   0:   iload_0   
   1:   istore_1     //istore_1其实是是有两部分组成,i表示int类型 ,1表示局部变量表中index为1。那合起来就是存储在局部变量表中,index为1的位置   
   2:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;         //引用常量池 #4;   
   5:   iload_1   
   6:   invokevirtual   #5; //Method java/io/PrintStream.println:(I)V     //引用常量池 #5;   
   9:   return  
  LineNumberTable:   
   line 21: 0  
   line 22: 2  
   line 23: 9  
  
  
public void getValue(int);   
  Code:   
   Stack=2, Locals=3, Args_size=2  
   0:   iload_1   
   1:   istore_2   
   2:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;   
   5:   iload_2   
   6:   invokevirtual   #5; //Method java/io/PrintStream.println:(I)V   
   9:   return  
  LineNumberTable:   
   line 26: 0  
   line 27: 2  
   line 28: 9  
  
  
public static void main(java.lang.String[]);   
  Code:   
   Stack=2, Locals=2, Args_size=1  
   0:   new     #6; //class Dog       解释:创建Dog   
   3:   dup       //复制引用到stack(栈)   
   4:   invokespecial   #7; //Method "<init>":()V   
   7:   bipush  10      //压入一个常量10   
   9:   invokevirtual   #8; //Method getValue:(I)V   
   12:  goto    20  
   15:  astore_1         
   16:  aload_1   
   17:  invokevirtual   #10; //Method java/lang/Exception.printStackTrace:()V   
   20:  return  
  Exception table:   
   from   to  target type   
     0    12    15   Class java/lang/Exception  //表示上面代码从1到12行之间如果发生Exception异常就goto到15处   
  
  
  
  LineNumberTable:   
   line 32: 0  
   line 35: 12  
   line 33: 15  
   line 34: 16  
   line 36: 20  
  
  
  
//如下这块我不太理解什么意思,加了try catch之后才出现这个的,还有如上的 Stack=    ,   
//这个不知道是什么意思,暂没有领悟 ,望高手指导
  StackMapTable: number_of_entries = 2  
   frame_type = 79 /* same_locals_1_stack_item */  
     stack = [ class java/lang/Exception ]   
   frame_type = 4 /* same */  
  
  
}  

 

   发表时间:2010-10-09  

  LZ写得不错,最近在看一下字节码方面的知识,对我补充很大。知识就是这样的,要多看不同人的叙述,比较体会愈深。

0 请登录后投票
   发表时间:2010-10-09  
共同 学习
0 请登录后投票
   发表时间:2010-10-09  
之前回复给beneo的一条PM,发这边看对楼主是否有帮助:
RednaxelaFX 写道
From: RednaxelaFX
To: beneo
Subject: Re: 反编译后frame same和frame append是什么


您好 ^_^

从Java 6开始,JVM规范有一个更新文档,JSR 202,里面提到一种新的字节码校验算法,“类型检查”;在此之前是用“类型推导”的算法。为了支持新算法,Class文件从版本50开始添加了一个新的属性表,叫做StackMapTable,里面记录的是一个方法中操作数栈与局部变量区的类型在一些特定位置的状态。这个规范更新将会被整合到JVM规范第三版中,可以预见会在今年内与JDK7一同发布。

您提到的帖子里的frame append、frame same等指的是StackMapTable中的stack map frame。这个frame跟Java方法调用栈的栈帧(stack frame)不是同一个东西,是描述栈帧状态的一种数据结构,只用于Class文件加载时的校验。注意所谓的frame append、frame chop都是对同一个方法的栈帧而言的,并不是说“添加栈帧”“减少栈帧”或者“压入栈帧”“弹出栈帧”,而是指栈帧的内容(局部变量之类)个数上有变化。

把规范中的一小段引用一下:
JSR 202 写道
4.8.4 The StackMapTable Attribute
The stack map attribute is a variable-length attribute in the attributes table of a Code attribute. The name of the attribute is StackMapTable. This attribute is used during the process of verification by typechecking (§4.11.1).

A stack map attribute consists of zero or more stack map frames. Each stack map frame specifies (either explicitly or implicitly) a bytecode offset, the verification types (§4.11.1) for the local variables, and the verification types for the operand stack.

The type checker deals with and manipulates the expected types of a method’s local variables and operand stack. Throughout this section, a location refers to either a single local variable or to a single operand stack entry.

We will use the terms stack map frame and type state interchangeably to describe a mapping from locations in the operand stack and local variables of a method to verification types. We will usually use the term stack map frame when such a mapping is provided in the class file, and the term type state when the mapping is inferred by the type checker.

...

Each stack_map_frame structure specifies the type state at a particular bytecode offset. Each frame type specifies (explicitly or implicitly) a value, offset_delta, that is used to calulate the actual bytecode offset at which it applies. The bytecode offset at which the frame applies is given by adding 1 + offset_delta to the offset of the previous frame, unless the previous frame is the initial frame of the method, in which case the bytecode offset is offset_delta.


每个map记录了一个字节码相对偏移量,一组局部变量的校验类型以及一组操作数栈的校验类型。它的定义如下:
union stack_map_frame {
    same_frame;
    same_locals_1_stack_item_frame;
    same_locals_1_stack_item_frame_extended;
    chop_frame;
    same_frame_extended;
    append_frame;
    full_frame;
}

same_frame {
    u1 frame_type = SAME;/* 0-63 */
}

same_locals_1_stack_item_frame {
    u1 frame_type = SAME_LOCALS_1_STACK_ITEM;/* 64-127 */
    verification_type_info stack[1];
}

same_locals_1_stack_item_frame_extended {
    u1 frame_type = SAME_LOCALS_1_STACK_ITEM_EXTENDED;/* 247 */
    u2 offset_delta;
    verification_type_info stack[1];
}

chop_frame {
    u1 frame_type=CHOP; /* 248-250 */
    u2 offset_delta;
}

same_frame_extended {
    u1 frame_type = SAME_FRAME_EXTENDED;/* 251*/
    u2 offset_delta;
}

append_frame {
    u1 frame_type = APPEND; /* 252-254 */
    u2 offset_delta;
    verification_type_info locals[frame_type -251];
}

full_frame {
    u1 frame_type = FULL_FRAME; /* 255 */
    u2 offset_delta;
    u2 number_of_locals;
    verification_type_info locals[number_of_locals];
    u2 number_of_stack_items;
    verification_type_info stack[number_of_stack_items];
}

留意到,所谓的“frame same”其实就是指一个stack map frame的第一个字节在0-63的范围内,意义是该frame与前一个frame的局部变量区、操作数栈上对应位置的类型都完全一样。frame append的意义是它对应的字节码偏移量位置上操作数栈为空,局部变量区的对应位置与前一个frame相同,但比前一个额外定义了k个局部变量,k = frame_type - 251;有append就有chop,也就是局部变量个数比前一个frame少的情况。其它frame_type对应的意义有空我写篇blog来说明吧 ^_^

前面提到“一些特定位置”,指的是每个“基本块”的开始位置。一个“基本块”(basic block)就是一个方法中的代码最长的直线型一段段代码序列。“直线型”也就是说代码序列中除了末尾之外不能有控制流(跳转)指令。
一个基本块的开头可以是方法的开头,也可以是某条跳转指令的跳转目标;
一个基本块的结尾可以是方法的末尾,也可以是某条跳转指令(Java中就是goto、if*系列等;invoke*系列的方法调用指令不算在跳转指令中)。如果一个方法代码如下:
public class Foo {
    public void foo() {
        // basic block 1 start
        int i = 0;
        int j = 0;
        if (i > 0) { // basic block 1 end
          // basic block 2 start
          int k = 0;
          // basic block 2 end
        }
        // basic block 3 start
        int l = 0;
        // basic block 3 end
    }
}

那么可以看到就有3个基本块。不过在Java Class文件里StackMapTable关心的是类型检查,为了进一步压缩这个表的大小,使用的基本块定义比通常的定义要更宽松些:一个条件跳转的直落分支与条件跳转前的代码算在同一个基本块内。于是前面的例子就变成:
public class Foo {
    public void foo() {
        // basic block 1 start
        int i = 0;
        int j = 0;
        if (i > 0) {
          int k = 0;
          // basic block 1 end
        }
        // basic block 2 start
        int l = 0;
        // basic block 2 end
    }
}


这个方法就会有一个StackMapTable属性表,其中有一个stack frame map记录(本来应该是两个,但第一个是隐式的,不记录在属性表里)。
public void foo();
  Code:
   Stack=1, Locals=4, Args_size=1
   /* basic block 1 start */
   0:   iconst_0
   1:   istore_1
   2:   iconst_0
   3:   istore_2
   4:   iload_1
   5:   ifle    10
   8:   iconst_0
   9:   istore_3
   /* basic block 1 end */
   /* basic block 2 start */
   10:  iconst_0 /* stack frame map 1 refers to here */
   11:  istore_3
   12:  return
   /* basic block 2 end */

  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   10      0      3    k       I
   0      13      0    this       LFoo;
   2      11      1    i       I
   4      9      2    j       I
   12      1      3    l       I

  StackMapTable: number_of_entries = 1
   frame_type = 253 /* append */
     offset_delta = 10
     locals = [ int, int ]

隐式的第一个基本块的stack frame map是从方法签名计算出来的。这个例子foo是个实例方法,没有显示声明的参数,所以参数个数是1,也就是隐藏参数this。那么在字节码偏移量0的位置上,操作数栈为空,
局部变量区:[ Foo ]

下一个基本块从字节码偏移量10开始。此处变量k已经过了作用域,所以局部变量区的有效内容应该是:
局部变量区:[ Foo, int, int ]
这就比前一个基本块开头处的状态多了2个局部变量,类型分别是[ int, int ],所以就有了上面对应的StackMapTable项了,253 - 251 = 2。

不知道这个解释是否足够解答您的疑问呢?
0 请登录后投票
   发表时间:2010-10-09  
非常感谢楼上这位兄弟提供的信息
0 请登录后投票
   发表时间:2010-10-09  
为啥public void getValue(int);    
  Code:    
   Stack=2, Locals=3, Args_size=2
这里的stack是2呢?
0 请登录后投票
   发表时间:2010-10-09  
我也好奇这个是怎么来的
0 请登录后投票
   发表时间:2010-10-09  
RednaxelaFX    貌似是阿里同事
0 请登录后投票
   发表时间:2010-10-09   最后修改:2010-10-09
yunzhiyifeng 写道
为啥public void getValue(int);    
  Code:    
   Stack=2, Locals=3, Args_size=2
这里的stack是2呢?

因为这样:
                        // 操作数栈(左边为栈顶)
                        // [ ]
0:   iload_1            // [ int ]
1:   istore_2           // [ ]
2:   getstatic      #4; // [ PrintStream ]
5:   iload_2            // [ int, PrintStream ]
6:   invokevirtual  #5; // [ ]
9:   return            // [ ]

注释里的是每条指令执行过后操作数栈的类型状态。在第一条指令之前还写了一行表示初始状态。
0 请登录后投票
   发表时间:2010-10-09  
public  void  getValue(int j) 
    { 
        int i=j; 
        System.out.println(i); 
    }
这段方法里面不是应该有this, i, j三个本地本量么,而stack是2,那哪个没有入栈?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics