java字节码文件件中为什么没有 localvariabletable

Java字节码问题求教,新手求帮助-中国学网-中国IT综合门户网站
> 信息中心 >
Java字节码问题求教,新手求帮助
来源:互联网 发表时间: 5:50:00 责任编辑:鲁晓倩字体:
为了帮助网友解决“Java字节码问题求教,新手求帮助”相关的问题,中国学网通过互联网对“Java字节码问题求教,新手求帮助”相关的解决方案进行了整理,用户详细问题包括:javaclass字节码首先贴出java代码public&class&Test&{private&int&i&=&0;private&String&s&=&"hello&world";public&void&say(String&s)&{System.out.println(s);&}}class文件字节码结果中国学网
其中method结构使用javap解析结果如下问题1:java/lang/Object&&init&这个方法是在哪定义的?为什么Object类里面找不到这个方法问题2:&init&方法中Code属性里面为什么出现了3次aload_0,a_load0指的是this当前类吗?局部变量表在字节码的哪里能看到问题3:&init&方法中Code属性里面的LineNumberTable属性看不懂,对于&line&1:&0我的理解是Code[0]对应Java代码中的第一行,但是感觉这样不对,而且后面也行不通最近刚刚开始学习分析字节码,很多地方还不是很懂,请各位大神帮帮我解决这些问题,在下不胜感激啊,具体解决方案如下:解决方案1:up&up&up,求各位高手指点解决方案2:谢邀,疑问逐个答复吧。1:java/lang/Object&&init&这个方法是在哪定义的?为什么Object类里面找不到这个方法在java规范中,每个类定义都需要有构造方法(构造器)的存在,如果没有显示声明,则在编译期间,由JVM自动生成,即楼主看到的&init&方法2、&init&方法中Code属性里面为什么出现了3次aload_0,a_load0指的是this当前类吗?局部变量表在字节码的哪里能看到。第1次aload_0是加载Test自身引用到栈顶,另外2次aload_0是由于要初始化实例变量i和s(),需要通过Test自身引用去取,所以需要先加载才能对相应变量进行初始化。关于局部变量表在字节码的哪里能看到,这个你得根据Class字节码规范、指令字节占用大小等信息去对照了。推荐一篇博文,可以详细看下:Class文件结构3、&init&方法中Code属性里面的LineNumberTable属性看不懂,对于&line&1:&0我的理解是Code[0]对应Java代码中的第一行,但是感觉这样不对,而且后面也行不通LineNumberTable用于存储字节码与源码的行数的对应关系,楼主哪里感觉不对呢?解决方案3:谢zhuangqingch的回答,仔细的看了一下,我是这么认为的,还请看一下对不对第一次aload_0将this引用压入栈顶,invokespecial在执行时又把this从栈中弹了出来第二次aload_0和iconst_0将this和0压入栈中,putfield在执行时又将两者弹了出来,相当于this.i=0LineNumberTable,是不是private&int&i&=&0;这句话编译后会形成&aload_0,iconst_0和putfield三条指令解决方案4:还要请教一个问题,我看了class文件结构之后知道了方法的局部变量表在字节码文件中是存放在methodref中的LocalVariableTable这个属性中的,可是为什么我的两个方法中都没有这个属性呢,say(String&s)这个方法命名是有参数的阿,为什么javap显示的属性中没有这个呢?
8个回答6个回答1个回答4个回答2个回答1个回答2个回答3个回答2个回答1个回答1个回答1个回答1个回答1个回答1个回答1个回答1个回答1个回答1个回答
相关文章:
最新添加资讯
24小时热门资讯
Copyright © 2004- All Rights Reserved. 中国学网 版权所有
京ICP备号-1 京公网安备02号> 种文件结构
种文件结构
niuxuang & &
发布时间: & &
浏览:38 & &
回复:0 & &
悬赏:0.0希赛币
类文件结构
  Class文件是一组以8位字节为基础单位的二进制流,各数据项严格按顺序排列其中,中间没有添加任何分隔符.
  根据JAVA虚拟机规范的规定,CLASS文件格式采用一种类似C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表。
  无符号数属于基本的数据类型,以u1,u2,u4,u8来分别表示一个字节,两个字节,四个字节和8个字节的无符号数,无符号数用来描述数字,索引引用,数量值或按照UTF8编码构成字符串数
  表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表都习惯性的以"_info"结尾,表用于描述有层次关系的复合结构的数据。整个CLASS文件本质上也是一张表。
minor_version
major_version
constant_pool_count
constant_pool
constant_pool_count-1
access_flags
this_class
super_class
interfaces_count
interfaces
interfaces_count
field_count
field_info
field_count
method_count
method_info
method_count
attribute_count
attribute_info
attributes
attribte_count
  魔数(magic)(4个字节)
  0xCAFEBABE
  次版本号(2个字节)
  主版本号(2个字节)
  高版本号的JDK可以向下兼容,但不能运行版本高于自己的CLASS文件。
  常量池计数器(2个字节)
  计数器从1而不是0开始,0x0016,十进制为22,代表有21个常量。索引为1~21.没有使用0索引是因为在后面某些指向常量池的索引可以通过0索引表示不引用任何一个常量池项目的意思。
  常量池
  常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)
  字面量的例子有文本字符串,被声明为final的常量值等。
  符号引用包含三类常量:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
  JAVA代码在进行JAVAC编译时,并不像C和C++那样有连接这一步骤,而是在虚拟机加载CLASS文件的时候进行动态连接。也就是说,在CLASS文件中不会保存各个方法和字段的最终内存布局信息,因此这些字段和方法的符号引用不经过转换的话是无法直接被虚拟机使用的。当虚拟机运行时,需要从常量池获得对应的符号引用,在类创建时或运行时解析时解析并翻译到具体的内存地址中。
  常量池中每一项常量都是一个表,共有11种,表开始的第一位都是一个字节的标志位,表明这个常量属于哪种类型。
  JAVA程序中不能定义超过64KB英文字符的变量和方法名,否则无法编译
  使用JAVAP工具可以分析class文件字节码
  javap -verbose TestClass
  访问标志
  用于识别一些类或接口的访问信息,包括这个CLASS是类还是接口,是否为public,是否为abstract等
  类索引、父类索引与接口索引集合
  用来确定继承关系
  字段表集合
  用于描述接口或类中声明的变量。字段field包括了类级变量或实例级变量,但不包括在方法内部声明的变量。不包括方法内部声明的变量.描述了该字段是否是Public,private, protected, static,final,volatile,transient等
  描述符用来描述字段的数据类型,方法的参数列表和返回值.用表示标识字符表示,对象类型用L+对象的全限定名表示
  方法表集合
  描述方法
  包括访问标志,名称索引,描述符索引,属性表索引。方法中的代码储存在方法属性表集合中一个名为Code的属性中。
  在JAVA虚拟机规范中,要重载一个方法,除了要与原方法具有相同的简单名称外,还要求必须拥有一个与原方法不同的特征签名,特征签名在JAVA代码层面和字节码层面有不同的定义。代码层面的签名只包括方法名称,参数顺序及参数类型,字节码层面还包括方法返回值和异常表。因此在CLASS文件中,如果两个方法有相同的名称和特征签名,但返回值不同,是合法的。
  属性表集合
  属性表集合的限制较少,不要求各个属性表有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息。JAVA虚拟机在运行时会忽略掉它不认识的属性。JAVA虚拟机规范中预定义了9项虚拟机实现应当能识别的属性:
JAVA代码编译成的字节码指令
ConstantValue
final关键字定义的常量值
Deprecated
类,方法表,字段表
被声明为deprecated的方法和字段
Exceptions
方法抛出的异常
InnerClasses
内部类列表
LineNumberTable
Java源码的行号与字节码指令的对应关系
LocalVariableTable
方法的局部变量描述
SourceFile
源文件名称
类,方法表,字段表
标识方法或字段为编译器自动生成的
  Code属性
  JAVA程序方法体中的代码经过Javac编译器处理之后,最终变为字节码指令存储在Code属性中。
  max_stack代表操作栈深度的最大值,在方法执行的任何时刻,操作数栈都不会超过这个深度,虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度。
  max_locals代表局部变量表所需的存储空间,单位为slot,对于byte,char,float,int,short,boolean,reference和returnAddress每个局部变量占用一个slot,而double和long需要两个slot.
  并不是方法中用到了多少个局部变量,就把这些局部变量所占的Slot之和作为max_locals的值,原因是局部变量表中的slot可以重用,当代码执行超过一个局部变量的作用域时,这个局部变量所占用的slot就可以被其他局部变量所使用。这个值编译器会自动计算得出
  code_length和code用来存储java源程序编译后生成的字节码指令,code_length代表字节码长度,code用于存储字节码指令的一系列字节流。字节码的每个指令就是一个字节。这样可以推出,一个字节最多可以代表256条指令, 目前已经使用了约200条。
  而code_length有4个字节,所以一个方法做多允许有65535条字节码指令,如果超过这个限制,javac就会拒绝编译,一般JSP可能会这个原因导致失败。
  在任何实例方法中,都可以通过this关键字访问到此方法所属的对象,它的底层实现就是通过javac编译器在编译的时候把this关键字的访问转变为对一个普通方法参数的访问。
  因此,任何实例方法的参数Args_size最少是1,而且locals最少也是1.而静态方法就可以为0了。
  异常表实际是Java代码的一部分,从start_pc行到end_pc行出现了类型为catch_type的异常,就转到第handler_pc行处理。这四个参数就组成了异常表。
  对于finally的实现,实际上就是对catch字段和前面对于任意情况都运行的异常表记录
  Exceptions属性
  表示方法可能抛出number_of_exceptions种受查异常,每种受查异常使用一个exception_index_table项表示
  LineNumberTable属性
  用于描述java源代码行号与字节码行号直接的对应关系。可以用-g:none或-g:lines选项来取消或要求生成这项信息,主要影响是报错时对战是否显示出错的行号。同时debug时无法设置断点。
  LocalVariableTable属性
  用于描述栈帧中局部变量表中的变量与源码中定义的变量之间的关系。也可以选择开关,关闭后果就是报错时看不到变量名称
  SourceFile属性
  用于记录生成这个Class文件的源码文件名称。可选
  ConstantValue属性
  通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量才可以使用这项属性。
  在JAVA中,int x=123;和static int x=123;的区别在于,非static类型的变量(实例变量)的赋值是在实例构造器&init&方法中进行的;而对于静态变量,则有两种方式可以选择:在类构造器&clinit&方法中进行,或者使用ConstantValue属性来赋值。目前SUN JAVAC编译器的选择是如果同时使用final和static来修饰一个变量,并且这个变量的数据类型是基本类型或者String的话,就生成ConstantValue属性来进行初始化,如果这个变量没有用final修饰,或者非以上类型,则选择在&clinit&中进行初始化。
  InnerClasses属性
  用于记录内部类与宿主类之间的关系
  Deprecated和synthetic属性
  都属于标志类布尔属性。deprecated表示在代码中使用@deprecated注释进行设置。synthetic表示字段或方法不是java源码产生,而是编译器自行添加的。
本问题标题:
本问题地址:
温馨提示:本问题已经关闭,不能解答。
暂无合适的专家
&&&&&&&&&&&&&&&
希赛网 版权所有 & &&Java字节码运行浅析
明白Java代码是如何编译成字节码并在JVM上运行的非常重要,这有助于理解程序运行的时候到底发生了些什么。理解这点不仅能搞清语言特性是如何实现的,并且在做方案讨论的时候能知道相应的副作用及权衡利弊。
本文介绍了Java代码是如何编译成字节码并在JVM上执行的。想了解JVM的内部结构以及字节码运行时用到的各个内存区域,可以看下我前面的一篇关于JVM内部细节的文章。
本文分为三部分,每一部分都分成几个小节。每个小节都可以单独阅读,不过由于一些概念是逐步建立起来的,如果你依次阅读完所有章节会更简单一些。每一节都会覆盖到Java代码中的不同结构,并详细介绍了它们是如何编译成字节码并执行的。
第一部分, 基础概念
JVM是一个基于栈的架构。方法执行的时候(包括main方法),在栈上会分配一个新的帧,这个栈帧包含一组局部变量。这组局部变量包含了方法运行过程中用到的所有变量,包括this引用,所有的方法参数,以及其它局部定义的变量。对于类方法(也就是static方法)来说,方法参数是从第0个位置开始的,而对于实例方法来说,第0个位置上的变量是this指针。
局部变量可以是以下这些类型:
除了long和double类型外,每个变量都只占局部变量区中的一个变量槽(slot),而long及double会占用两个连续的变量槽,因为这些类型是64位的。
当一个新的变量创建的时候,操作数栈(operand stack)会用来存储这个新变量的值。然后这个变量会存储到局部变量区中对应的位置上。如果这个变量不是基础类型的话,本地变量槽上存的就只是一个引用。这个引用指向堆的里一个对象。
int i = 5;
编译后就成了
2: istore_0
用来将一个字节作为整型数字压入操作数栈中,在这里5就会被压入操作数栈上。
这是istore_这组指令集(译注:严格来说,这个应该叫做操作码,opcode ,指令是指操作码加上对应的操作数,oprand。不过操作码一般作为指令的助记符,这里统称为指令)中的一条,这组指令是将一个整型存储到本地变量中。代表的是局部变量区中的位置,并且只能是0,1,2,3。再高的话只能用另一条指令istore了,这条指令会接受一个操作数,对应的是局部变量区中的位置信息。
当这条指令执行的时候,内存布局是这样的:
class文件中的每一个方法都会包含一个局部变量表,如果这段代码在一个方法里面的话,你在类文件的局部变量表中会找到如下的一条记录。
LocalVariableTable:
Java类里面的字段是作为类对象实例的一部分,存储在堆里面的(类变量对应存储在类对象里面)。关于字段的信息会添加到类文件里的field_info数组里,像下面这样:
ClassFile {
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info contant_pool[constant_pool_count & 1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
另外,如果变量被初始化了,那么初始化的字节码会加到构造方法里。
下面这段代码编译了之后:
public class SimpleClass {
public int simpleField = 100;
如果你用javap进行反编译,这个被添加到了field_info数组里的字段会多出一段描述信息。
public int simpleField;
Signature: I
flags: ACC_PUBLIC
初始化变量的字节码会被加到构造方法里,像下面这样:
public SimpleClass();
Signature: ()V
flags: ACC_PUBLIC
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1
// Method java/lang/Object.&&init&&:()V
4: aload_0
7: putfield
// Field simpleField:I
10: return
从局部变量数组中加载一个对象引用到操作数栈的栈顶。尽管这段代码看起来没有构造方法,但是在编译器生成的默认的构造方法里,就会包含这段初始化的代码。第一个局部变量正好是this引用,于是aload_0把this引用压到操作数栈中。aload_0是aload_指令集中的一条,这组指令会将引用加载到操作数栈中。n对应的是局部变量数组中的位置,并且也只能是0,1,2,3。还有类似的加载指令,它们加载的并不是对象引用,比如iload_,lload_,fload_,和dload_, 这里i代表int,l代表long,f代表float,d代表double。局部变量的在数组中的位置大于3的,得通过iload,lload,fload,dload,和aload进行加载,这些指令都接受一个操作数,它代表的是要加载的局部变量的在数组中的位置。
invokespecial
这条指令可以用来调用对象实例的初始化方法,私有方法和父类中的方法。它是方法调用指令集中的一条,其它的还有invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual.这里的invokespecial 指令调用的是父类也就是java.lang.Objectr构造方法。
它是用来把一个字节作为整型压到操作数栈中的,在这里100会被加到操作栈里面。
它接受一个操作数,这个数引用的是运行时常量池里的一个字段,在这里这个字段是simpleField。赋给这个字段的值,以及包含这个字段的对象引用,在执行这条指令的时候,都 会从操作数栈顶上pop出来。前面的aload_0指令已经把包含这个字段的对象压到操作数栈上了,而后面的bipush又把100压到栈里。最后putfield指令会将这两个值从栈顶弹出。执行完的结果就是这个对象的simpleField这个字段的值更新成了100。
上述代码执行的时候内存里面是这样的:
这里的putfield指令的操作数引用的是常量池里的第二个位置。JVM会为每种类型维护一个常量池,运行时的数据结构有点类似一个符号表,尽管它包含的信息更多。Java中的字节码操作需要数据,但通常这些数据都太大了,存储在字节码里不适合,它们会被存储在常量池里面,而字节码包含一个常量池里的引用 。当类文件生成的时候,其中的一块就是常量池:
Constant pool:
#1 = Methodref
java/lang/Object.&&init&&:()V
#2 = Fieldref
SimpleClass.simpleField:I
#3 = Class
SimpleClass
#4 = Class
java/lang/Object
simpleField
#10 = Utf8
LineNumberTable
#11 = Utf8
LocalVariableTable
#12 = Utf8
#13 = Utf8
SimpleClass
#14 = Utf8
SourceFile
#15 = Utf8
SimpleClass.java
#16 = NameAndType
&&init&&:()V
#17 = NameAndType
simpleField:I
#18 = Utf8
LSimpleClass;
#19 = Utf8
java/lang/Object
常量字段(类常量)
带有final标记的常量字段在class文件里会被标记成ACC_FINAL.
public class SimpleClass {
public final int simpleField = 100;
字段的描述信息会标记成ACC_FINAL:
public static final int simpleField = 100;
Signature: I
flags: ACC_PUBLIC, ACC_FINAL
ConstantValue: int 100
对应的初始化代码并不变:
4: aload_0
7: putfield
// Field simpleField:I
带有static修饰符的静态变量则会被标记成ACC_STATIC:
public static int simpleField;
Signature: I
flags: ACC_PUBLIC, ACC_STATIC
不过在实例的构造方法中却再也找 不到对应的初始化代码了。因为static变量会在类的构造方法 中进行初始化,并且它用的是putstatic指令而不是putfiled。
static {};
Signature: ()V
flags: ACC_STATIC
stack=1, locals=0, args_size=0
2: putstatic
// Field simpleField:I
标签(Tag):
------分隔线----------------------------
------分隔线----------------------------}

我要回帖

更多关于 获取字节码文件 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信