精华内容
下载资源
问答
  • 深入理解java编译后字节码文件

    千次阅读 2018-04-19 17:15:53
    从我们写的java文件到通过编译器编译成java字节码文件(也就是.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由哪种编译器编译...

    也许你写了无数行的代码,也许你能非常溜的使用高级语言,但是你未必了解那些高级语言的执行过程。例如大行其道的Java。

    Java号称是一门“一次编译到处运行”的语言,但是我们对这句话的理解深度又有多少呢?从我们写的java文件到通过编译器编译成java字节码文件(也就是.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由哪种编译器编译,甚至是手写字节码文件,只要符合java虚拟机的规范,那么它就能够执行该字节码文件。那么本文主要讲讲java字节码文件相关知识。接下来我们通过具体的Demo来深入理解:

    1 首先我们来写一个java源文件

    javasrc.png

    上面是我们写的一个java程序,很简单,只有一个成员变量a以及一个方法testMethod() 。

    2 接下来我们用javac命令或者ide工具将该java源文件编译成java字节码文件。

    demo.png

    上图是编译好的字节码文件,我们可以看到一堆16进制的字节。如果你使用IDE去打开,也许看到的是已经被反编译的我们所熟悉的java代码,而这才是纯正的字节码,这也是我们今天需要讲的内容重点。

    也许你会对这样一堆字节码感到头疼,不过没关系,我们慢慢试着你看懂它,或许有不一样的收获。在开始之前我们先来看一张图

    java_byte.jpeg

    这张图是一张java字节码的总览图,我们也就是按照上面的顺序来对字节码进行解读的。一共含有10部分,包含魔数,版本号,常量池等等,接下来我们按照顺序一步一步解读。

    3.1 魔数

    从上面的总览图中我们知道前4个字节表示的是魔数,对应我们Demo的是 0XCAFE BABE。什么是魔数?魔数是用来区分文件类型的一种标志,一般都是用文件的前几个字节来表示。比如0XCAFE BABE表示的是class文件,那么有人会问,文件类型可以通过文件名后缀来判断啊?是的,但是文件名是可以修改的(包括后缀),那么为了保证文件的安全性,将文件类型写在文件内部来保证不被篡改。
    从java的字节码文件类型我们看到,CAFE BABE翻译过来是咖啡宝贝之意,然后再看看java图标。

    java_icon.png

    CAFE BABE = 咖啡。

    3.2 版本号

    我们识别了文件类型之后,接下来要知道版本号。版本号含主版本号和次版本号,都是各占2个字节。在此Demo种为0X0000 0033。其中前面的0000是次版本号,后面的0033是主版本号。通过进制转换得到的是次版本号为0,主版本号为51。
    从oracle官方网站我们能够知道,51对应的正式jdk1.7,而其次版本为0,所以该文件的版本为1.7.0。如果需要验证,可以在用java –version命令输出版本号,或者修改编译目标版本–target重新编译,查看编译后的字节码文件版本号是否做了相应的修改。

    至此,我们共了解了前8字节的含义,下面讲讲常量池相关内容。

    3.3 常量池

    紧接着主版本号之后的就是常量池入口。常量池是Class文件中的资源仓库,在接下来的内容中我们会发现很多地方会涉及,如Class Name,Interfaces等。常量池中主要存储2大类常量:字面量和符号引用。字面量如文本字符串,java中声明为final的常量值等等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符。

    为什么需要类和接口的全局限定名呢?系统引用类或者接口的时候不是通过内存地址进行操作吗?这里大家仔细想想,java虚拟机在没有将类加载到内存的时候根本都没有分配内存地址,也就不存在对内存的操作,所以java虚拟机首先需要将类加载到虚拟机中,那么这个过程设计对类的定位(需要加载A包下的B类,不能加载到别的包下面的别的类中),所以需要通过全局限定名来判别唯一性。这就是为什么叫做全局,限定的意思,也就是唯一性。

    在进行具体常量池分析之前,我们先来了解一下常量池的项目类型表:

    jvm_constant.png

    上面的表中描述了11中数据类型的结构,其实在jdk1.7之后又增加了3种(CONSTANT_MethodHandle_info,CONSTANT_MethodType_info以及CONSTANT_InvokeDynamic_info)。这样算起来一共是14种。接下来我们按照Demo的字节码进行逐一翻译。

    0×0015:由于常量池的数量不固定(n+2),所以需要在常量池的入口处放置一项u2类型的数据代表常量池数量。因此该16进制是21,表示有20项常量,索引范围为1~20。明明是21,为何是20呢?因为Class文件格式规定,设计者就讲第0项保留出来了,以备后患。从这里我们知道接下来我们需要翻译出20项常量。
    Constant #1 (一共有20个常量,这是第一个,以此类推…)
    0x0a-:从常量类型表中我们发现,第一个数据均是u1类型的tag,16进制的0a是十进制的10,对应表中的MethodRef_info。
    0x-00 04-:Class_info索引项#4
    0x-00 11-:NameAndType索引项#17
    Constant #2
    0x-09: FieldRef_info
    0×0003 :Class_info索引项#3
    0×0012:NameAndType索引项#18
    Constant #3
    0×07-: Class_info
    0x-00 13-: 全局限定名常量索引为#19
    Constant #4
    0x-07 :Class_info
    0×0014:全局限定名常量索引为#20
    Constant #5
    0×01:Utf-8_info
    0x-00 01-:字符串长度为1(选择接下来的一个字节长度转义)
    0x-61:”a”(十六进制转ASCII字符)
    Constant #6
    0×01:Utf-8_info
    0x-00 01:字符串长度为1
    0x-49:”I”
    Constant #7
    0×01:Utf-8_info
    0x-00 06:字符串长度为6
    0x-3c 696e 6974 3e-:”<init>”
    Constant #8
    0×01 :UTF-8_info
    0×0003:字符串长度为3
    0×2829 56:”()V”
    Constant #9
    0x-01:Utf-8_info
    0×0004:字符串长度为4
    0x436f 6465:”Code”
    Constant #10
    0×01:Utf-8_info
    0×00 0f:字符串长度为15
    0x4c 696e 654e 756d 6265 7254 6162 6c65:”LineNumberTable”
    Constant #11
    ox01: Utf-8_info
    0×00 12字符串长度为18
    0x-4c 6f63 616c 5661 7269 6162 6c65 5461 626c 65:”LocalVariableTable”
    Constant #12
    0×01:Utf-8_info
    0×0004 字符串长度为4
    0×7468 6973 :”this”
    Constant #13
    0×01:Utf-8_info
    0x0f:字符串长度为15
    0x4c 636f 6d2f 6465 6d6f 2f44 656d 6f3b:”Lcom/demo/Demo;”
    Constant #14
    0×01:Utf-8_info
    0×00 0a:字符串长度为10
    ox74 6573 744d 6574 686f 64:”testMethod”
    Constant #15
    0×01:Utf-8_info
    0x000a:字符串长度为10
    0x536f 7572 6365 4669 6c65 :”SourceFile”
    Constant #16
    0×01:Utf-8_info
    0×0009:字符串长度为9
    0x-44 656d 6f2e 6a61 7661 :”Demo.java”
    Constant #17
    0x0c :NameAndType_info
    0×0007:字段或者名字名称常量项索引#7
    0×0008:字段或者方法描述符常量索引#8
    Constant #18
    0x0c:NameAndType_info
    0×0005:字段或者名字名称常量项索引#5
    0×0006:字段或者方法描述符常量索引#6
    Constant #19
    0×01:Utf-8_info
    0×00 0d:字符串长度为13
    0×63 6f6d 2f64 656d 6f2f 4465 6d6f:”com/demo/Demo”
    Constant #20
    0×01:Utf-8_info
    0×00 10 :字符串长度为16
    0x6a 6176 612f 6c61 6e67 2f4f 626a 6563 74 :”java/lang/Object”
    到这里为止我们解析了所有的常量。接下来是解析访问标志位。

    3.4 Access_Flag 访问标志

    访问标志信息包括该Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final。通过上面的源代码,我们知道该文件是类并且是public。

    access_flag.png

    0x 00 21:是0×0020和0×0001的并集。其中0×0020这个标志值涉及到了字节码指令,后期会有专题对字节码指令进行讲解。期待中……

    3.5 类索引

    类索引用于确定类的全限定名
    0×00 03 表示引用第3个常量,同时第3个常量引用第19个常量,查找得”com/demo/Demo”。#3.#19

    3.6父类索引

    0×00 04 同理:#4.#20(java/lang/Object)

    3.7 接口索引

    通过java_byte.jpeg图我们知道,这个接口有2+n个字节,前两个字节表示的是接口数量,后面跟着就是接口的表。我们这个类没有任何接口,所以应该是0000。果不其然,查找字节码文件得到的就是0000。

    3.8 字段表集合

    字段表用于描述类和接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量。
    同样,接下来就是2+n个字段属性。我们只有一个属性a,按道理应该是0001。查找文件果不其然是0001。
    那么接下来我们要针对这样的字段进行解析。附上字段表结构图

    字段表结构.png

    0×00 02 :访问标志为private(自行搜索字段访问标志)
    0×00 05 : 字段名称索引为#5,对应的是”a”
    0x 00 06 :描述符索引为#6,对应的是”I”
    0x 00 00 :属性表数量为0,因此没有属性表。
    tips:一些不太重要的表(字段,方法访问标志表)可以自行搜索,这里就不贴出来了,防止篇幅过大。

    3.9 方法

    我们只有一个方法testMethod,按照道理应该前2个字节是0001。通过查找发现是0×00 02。这是什么原因,这代表着有2个方法呢?且继续看……

    方法表结构.png

    上图是一张方法表结构图,按照这个图我们分析下面的字节码:

    第1个方法:

    0×00 01:访问标志 ACC_PUBLIC,表明该方法是public。(可自行搜索方法访问标志表)
    0×00 07:方法名索引为#7,对应的是”<init>”
    0×00 08:方法描述符索引为#8,对应的是”()V”
    0×00 01:属性表数量为1(一个属性表)
    那么这里涉及到了属性表。什么是属性表呢?可以这么理解,它是为了描述一些专有信息的,上面的方法带有一张属性表。所有属性表的结构如下图:
    一个u2的属性名称索引,一个u2的属性长度加上属性长度的info。
    虚拟机规范预定义的属性有很多,比如Code,LineNumberTable,LocalVariableTable,SourceFile等等,这个网上可以搜索到。

    属性表结构.png

    按照上面的表结构解析得到下面信息:
    0×0009:名称索引为#9(“Code”)。
    0×000 00038:属性长度为56字节。
    那么接下来解析一个Code属性表,按照下图解析

    code.png

    前面6个字节(名称索引2字节+属性长度4字节)已经解析过了,所以接下来就是解析剩下的56-6=50字节即可。
    0×00 02 :max_stack=2
    0×00 01 : max_locals=1
    0×00 0000 0a : code_length=10
    0x2a b700 012a 04b5 0002 b1 : 这是code代码,可以通过虚拟机字节码指令进行查找。
    2a=aload_0(将第一个引用变量推送到栈顶)
    b7=invokespecial(调用父类构造方法)
    00=什么都不做
    01 =将null推送到栈顶
    2a=同上
    04=iconst_1 将int型1推送到栈顶
    b5=putfield 为指定的类的实例变量赋值
    00= 同上
    02=iconst_m1 将int型-1推送栈顶
    b1=return 从当前方法返回void
    整理,去除无动作指令得到下面
    0 : aload_0
    1 : invokespecial
    4 : aload_0
    5 : iconst_1
    6 : putfield
    9 : return
    关于虚拟机字节码指令这块内容,后期会继续深入下去…… 目前只需要了解即可。接下来顺着Code属性表继续解析下去:
    0×00 00 : exception_table_length=0
    0×00 02 : attributes_count=2(Code属性表内部还含有2个属性表)
    0×00 0a: 第一个属性表是”LineNumberTable”

    LineNumberTable.png

    0×00 0000 0a : “属性长度为10″
    0×00 02 :line_number_table_length=2
    line_number_table是一个数量为line_number_table_length,类型为line_number_info的集合,line_number_info表包括了start_pc和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源码行号
    0×00 00 : start_pc =0
    0×00 03 : end_pc =3
    0×00 04 : start_pc=4
    0×00 04 : end_pc=4

    0×00 0b 第二个属性表是:”LocalVariableTable”


    local_variable_table.png

    local_variable_info.png

    0×00 0000 0c:属性长度为12
    0×00 01 : local_variable_table_length=1
    然后按照local_variable_info表结构进行解析:
    0×00 00 : start_pc=0
    0×00 0a:length=10
    0x000c : name_index=”this”
    0x000d : descriptor_index #13 (“Lcom/demo/Demo”)
    0000 index=0
    //——-到这里第一个方法就解析完成了——-//
    Method(<init>)–1个属性Code表-2个属性表(LineNumberTable ,LocalVariableTable)接下来解析第二个方法

    第2个方法:

    0×00 04:”protected”
    0×00 0e: #14(”testMethod”)
    0×00 08 : “()V”
    0×0001 : 属性数量=1
    0×0009 :”Code”
    0×0000 002b 属性长度为43
    解析一个Code表
    0000 :max_stack =0
    0001 : max_local =1
    0000 0001 : code_length =1
    0xb1 : return(该方法返回void)
    0×0000 异常表长度=0
    0×0002 属性表长度为2
    //第一个属性表
    0x000a : #10,LineNumberTable
    0×0000 0006 : 属性长度为6
    0×0001 : line_number_length = 1
    0×0000 : start_pc =0
    0×0008 : end_pc =8
    //第二个属性表
    0x000b : #11 ,LocalVariableTable
    0×0000 000c : 属性长度为12
    0×0001 : local_variable_table_length =1
    0×0000 :start_pc = 0
    0×0001: length = 1
    0x000c : name_index =#12 “this”
    0x000d : 描述索引#13 “Lcom/demo/Demo;”
    0000 index=0

    //到这里为止,方法解析都完成了,回过头看看顶部解析顺序图,我们接下来就要解析Attributes了。

    3.10 Attribute

    0×0001 :同样的,表示有1个Attributes了。
    0x000f : #15(“SourceFile”)
    0×0000 0002 attribute_length=2
    0×0010 : sourcefile_index = #16(“Demo.java”)
    SourceFile属性用来记录生成该Class文件的源码文件名称。

    source_file.jpeg

    4 另话

    其实,我们写了这么多确实很麻烦,不过这种过程自己体验一遍的所获所得还是不同的。现在,使用java自带的反编译器来解析字节码文件。
    javap -verbose Demo //不用带后缀.class

    javap_result.png

    5 总结

    到此为止,讲解完成了class文件的解析,这样以后我们也能看懂字节码文件了。了解class文件的结构对后面进一步了解虚拟机执行引擎非常重要,所以这是基础并重要的一步。

    展开全文
  • 1,源码编译字节码 2,字节码编译成本地机器码(符合本地系统专属的指令) 解释执行也包括两种情况: 1,源码解释执行 2,字节码解释执行 解释和编译执行的区别是:是否产生中间本地机器码。 一、编译过程: 大...

    编译包括两种情况:
    1,源码编译成字节码
    2,字节码编译成本地机器码(符合本地系统专属的指令)
    解释执行也包括两种情况:
    1,源码解释执行
    2,字节码解释执行

    解释和编译执行的区别是:是否产生中间本地机器码。

    一、编译过程:

    大部分的程序代码从开始编译到最终转化成物理机的目标代码或虚拟机能执行的指令集之前,都会按照如下图所示的各个步骤进行:

    其中绿色的模块可以选择性实现。

    • 上图中间的那条分支是解释执行的过程(即一条字节码一条字节码地解释执行,如JavaScript),
    • 而下面的那条分支就是传统编译原理中从源代码到目标机器代码的生成过程。

    二、现代经典编译原理的思路:

    在执行前先对程序源码进行词法解析和语法解析处理,把源码转化为抽象语法树。

    对于一门具体语言的实现来说:

    • 词法和语法分析乃至后面的优化器和目标代码生成器都可以选择独立于执行引擎,形成一个完整意义的编译器去实现,这类代表是C/C++语言。
    • 也可以把抽象语法树或指令流之前的步骤实现一个半独立的编译器,这类代表是Java语言。
    • 又或者可以把这些步骤和执行引擎全部集中在一起实现,如大多数的JavaScript执行器。

    三、Javac编译

    在Java中提到“编译”,自然很容易想到Javac编译器将*.java文件编译成为*.class文件的过程,

    1,这里的Javac编译器称为前端编译器,其他的前端编译器还有诸如Eclipse JDT中的增量式编译器ECJ等。
    2,相对应的还有后端编译器,它在程序运行期间将字节码转变成机器码(现在的Java程序在运行时基本都是解释执行加编译执行),
       如HotSpot虚拟机自带的JIT(Just In Time Compiler)编译器(分Client端和Server端)。
    3,有时候还有静态提前编译器(AOT,Ahead Of Time Compiler)直接把*.java文件编译成本地机器代码,如GCJ、Excelsior JET等,这类编译器我们应该比较少遇到。
    

     Javac编译(前端编译)的过程:

    1,词法分析

    词法分析是将源代码的字符流转变为标记(Token)集合。

    单个字符是程序编写过程中的的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符等都可以成为标记,
    比如整型标志int由三个字符构成,但是它只是一个标记,不可拆分。

     2、语法分析

    语法分析是根据Token序列来构造抽象语法树的过程。

    抽象语法树是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构,如类型、修饰符、运算符等。

    经过这个步骤后,编译器就基本不会再对源码文件进行操作了,后续的操作都建立在抽象语法树之上。

    3、填充符号表

    完成了语法分析和词法分析之后,下一步就是填充符号表的过程。

    符号表是由一组符号地址和符号信息构成的表格。

    符号表中所登记的信息在编译的不同阶段都要用到,在语义分析(后面的步骤)中,符号表所登记的内容将用于语义检查和产生中间代码,在目标代码生成阶段,对符号名进行地址分配时,符号表是地址分配的依据。

    比如:默认构造器的添加。

    4、语义分析

    语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的。

    而语义分析的主要任务是:

    对结构上正确的源程序进行上下文有关性的审查。

     语义分析过程分为标注检查和数据及控制流分析两个步骤:

    1,标注检查的内容包括诸如:变量使用前是否已被声明、变量和赋值之间的数据类型是否匹配等。
    2,数据及控制流分析是对程序上下文逻辑更进一步的验证,检查出诸如:程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。

    5、字节码生成 

    字节码生成阶段不仅仅是把前面各个步骤所生成的信息转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。 

    • 实例构造器<init>()方法和类构造器<clinit>()方法就是在这个阶段添加到语法树之中的
    • 这里的实例构造器并不是指默认的构造函数,而是指我们自己重载的构造函数,

    如果用户代码中没有提供任何构造函数,那编译器会自动添加一个没有参数、访问权限与当前类一致的默认构造函数,这个工作在填充符号表阶段就已经完成了。

    四、JIT编译

    1、即时编译的产生:

    Java程序最初是仅仅通过解释器解释执行的,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。

    于是后来在虚拟机中引入了JIT编译器(即时编译器),
    当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),
    为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器。

    PS:区别是:即时编译生成机器相关的中间码,可重复执行缓存效率高。解释执行直接执行字节码,重复执行需要重复解释。

    现在主流的商用虚拟机(如Sun HotSpot、IBM J9)中几乎都同时包含解释器和编译器

    (三大商用虚拟机之一的JRockit是个例外,它内部没有解释器,因此会有启动相应时间长,但它主要是面向服务端的应用,这类应用一般不会重点关注启动时间)。

    二者各有优势:

    • 当程序需要迅速启动和执行时,解释器可以首先发挥作用,省去编译的时间,立即执行;
    • 当程序运行后,随着时间的推移,编译器逐渐会失去作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率。

    解释执行可以节约内存,而编译执行可以提升效率。

    目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器直接配合的方式工作。

    2、运行过程中会被即时编译器编译的“热点代码”有两类:

    被多次调用的方法。
    被多次调用的循环体。

    两种情况,编译器都是以整个方法作为编译对象,这种编译也是虚拟机中标准的编译方式。

    一段代码或方法是不是热点代码,是不是需要触发即时编译,需要进行Hot Spot Detection(热点探测)。

    3、目前主要的热点 判定方式有以下两种:

    3.1、基于采样的热点探测:

    虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这段方法代码就是“热点代码”。
    • 好处是:实现简单高效,还可以很容易地获取方法调用关系,
    • 缺点是:很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。

    3.2、基于计数器的热点探测: 

    虚拟机会为每个方法,甚至是代码块建立计数器,统计方法的执行次数,
    如果执行次数超过一定的阀值,就认为它是“热点方法”。

    这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。

    4、在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法两个计数器

    方法调用计数器和回边计数器。

    4.1、方法调用计数器:

    用来统计方法调用的次数,在默认设置下,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间内方法被调用的次数。

    4.2、回边计数器:

    用于统计一个方法中循环体代码执行的次数.
    (准确地说,应该是回边的次数,因为并非所有的循环都是回边),
    在字节码中遇到控制流向后跳转的指令就称为“回边”。

    在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阀值,当计数器的值超过了阀值,就会触发JIT编译。

    5、即时编译和解释执行的执行顺序:

    • 触发了JIT编译后,在默认设置下,执行引擎并不会同步等待编译请求完成,
    • 而是继续进入解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成为止(编译工作在后台线程中进行)。
    • 当编译工作完成后,下一次调用该方法或代码时,就会使用已编译的版本。

    6、方法调用计数器触发即时编译的流程:

    方法计数器触发即时编译的过程与回边计数器触发即时编译的过程类似

    展开全文
  • Java字节码Java源文件编译产生的中间文件java虚拟机是可运行java字节码的假想计算机 java的跨平台性也是相对与其他编程语言而言的先介绍一下c语言的编译过程吧先是C语言源程序 也就是c的文件经过C编译程序编译后,...
    Java字节码是Java源文件编译产生的中间文件
    java虚拟机是可运行java字节码的假想计算机 java的跨平台性也是相对与其他编程语言而言的
    先介绍一下c语言的编译过程吧先是C语言源程序 也就是c的文件经过C编译程序编译后,生成windows可执行文件exe文件,然后在windows中执行。再介绍java的编译过程先是java源程序扩展名为java的文件,由java编译程序java字节码文件,就是class文件然后在java虚拟机中执行。机器码是由CPU来执行的。Java编译后是字节码, 电脑只能运行机器码。Java在运行的时候把字节码变成机器码。C/C++在编译的时候直接编译成机器码
    展开全文
  • java编译查看字节码

    千次阅读 2014-04-01 22:42:38
    jdk中的bin里面自带有反编译的程序,叫javap.exe,利用他可以从编译生成的.class查看出对应的字节码代码 具体方法如下: 1.javap -verbose ** >> **.txt 例: 随便写个java类Person class Person{ String name;...

    深入理解字节码点击这里 

    jdk中的bin里面自带有反编译的程序,叫javap.exe,利用他可以从编译生成的.class查看出对应的字节码代码

    具体方法如下:

    1.javap -verbose ** >> **.txt

    例:

    随便写个java类Person

    class Person{
    	String name;	
    	char sex;
    	int age;
    	Person(){
    	}
    	Person(String name,char sex,int age){
    	this.name=name;
    	this.sex=sex;
    	this.age=age;	
    	}
    	String toString(int a){
    	return(String.valueOf(a));
    	}
    	String toString(char a){
    	return(String.valueOf(a));
    	}
    }

    编译生成.class后,放到桌面上,shift+右键在当前窗口打开命令行(win7可以,xp不行,就是去.class对应的目录输入命令)输入
    javap -verbose Person >> Person.txt
    然后在桌面上就看到了Person.txt

    内容:

    Classfile /C:/Users/linux_v/Desktop/Person.class
      Last modified Oct 11, 2012; size 589 bytes
      MD5 checksum 137de4fcce084d25ffb5c93b1708f63a
      Compiled from "Person.java"
    class Person
      SourceFile: "Person.java"
      minor version: 0
      major version: 51
      flags: ACC_SUPER
    
    Constant pool:
       #1 = Methodref          #8.#25         //  java/lang/Object."<init>":()V
       #2 = Fieldref           #7.#26         //  Person.name:Ljava/lang/String;
       #3 = Fieldref           #7.#27         //  Person.sex:C
       #4 = Fieldref           #7.#28         //  Person.age:I
       #5 = Methodref          #29.#30        //  java/lang/String.valueOf:(I)Ljava/lang/String;
       #6 = Methodref          #29.#31        //  java/lang/String.valueOf:(C)Ljava/lang/String;
       #7 = Class              #32            //  Person
       #8 = Class              #33            //  java/lang/Object
       #9 = Utf8               name
      #10 = Utf8               Ljava/lang/String;
      #11 = Utf8               sex
      #12 = Utf8               C
      #13 = Utf8               age
      #14 = Utf8               I
      #15 = Utf8               <init>
      #16 = Utf8               ()V
      #17 = Utf8               Code
      #18 = Utf8               LineNumberTable
      #19 = Utf8               (Ljava/lang/String;CI)V
      #20 = Utf8               toString
      #21 = Utf8               (I)Ljava/lang/String;
      #22 = Utf8               (C)Ljava/lang/String;
      #23 = Utf8               SourceFile
      #24 = Utf8               Person.java
      #25 = NameAndType        #15:#16        //  "<init>":()V
      #26 = NameAndType        #9:#10         //  name:Ljava/lang/String;
      #27 = NameAndType        #11:#12        //  sex:C
      #28 = NameAndType        #13:#14        //  age:I
      #29 = Class              #34            //  java/lang/String
      #30 = NameAndType        #35:#21        //  valueOf:(I)Ljava/lang/String;
      #31 = NameAndType        #35:#22        //  valueOf:(C)Ljava/lang/String;
      #32 = Utf8               Person
      #33 = Utf8               java/lang/Object
      #34 = Utf8               java/lang/String
      #35 = Utf8               valueOf
    {
      java.lang.String name;
        flags: 
    
    
      char sex;
        flags: 
    
    
      int age;
        flags: 
    
    
      Person();
        flags: 
    
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0       
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return        
          LineNumberTable:
            line 5: 0
            line 6: 4
    
      Person(java.lang.String, char, int);
        flags: 
    
        Code:
          stack=2, locals=4, args_size=4
             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 sex:C
            14: aload_0       
            15: iload_3       
            16: putfield      #4                  // Field age:I
            19: return        
          LineNumberTable:
            line 7: 0
            line 8: 4
            line 9: 9
            line 10: 14
            line 11: 19
    
      java.lang.String toString(int);
        flags: 
    
        Code:
          stack=1, locals=2, args_size=2
             0: iload_1       
             1: invokestatic  #5                  // Method java/lang/String.valueOf:(I)Ljava/lang/String;
             4: areturn       
          LineNumberTable:
            line 13: 0
    
      java.lang.String toString(char);
        flags: 
    
        Code:
          stack=1, locals=2, args_size=2
             0: iload_1       
             1: invokestatic  #6                  // Method java/lang/String.valueOf:(C)Ljava/lang/String;
             4: areturn       
          LineNumberTable:
            line 16: 0
    }
    
    看着挺高端的,有时间再慢慢研究,还有另一种命令

    2.javap –c ** >> **.txt

    上面链接给的测试类,我们确实时常讨论String,StringBuilder,StringBuffer之间的区别,其实看字节码确实能看出一点区别,即使不懂字节码。

    TestString.java

    public class TestString { 
        public String testString(String str1, String str2){ 
           return str1 + str2; 
        } 
        public String testStringBuffer(StringBuffer sb, String str){ 
           return sb.append(str).toString(); 
        }
        public String testStringBuilder(StringBuilder sb, String str){
    	return sb.append(str).toString();
        } 
    } 
    编译生成字节码后,输入
    javap -c TestString >> TestString.txt
    生成文本如下:

    Compiled from "TestString.java"
    public class TestString {
      public TestString();
        Code:
           0: aload_0       
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return        
    
      public java.lang.String testString(java.lang.String, java.lang.String);
        Code:
           0: new           #2                  // class java/lang/StringBuilder
           3: dup           
           4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
           7: aload_1       
           8: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          11: aload_2       
          12: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          15: invokevirtual #5                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          18: areturn       
    
      public java.lang.String testStringBuffer(java.lang.StringBuffer, java.lang.String);
        Code:
           0: aload_1       
           1: aload_2       
           2: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
           5: invokevirtual #7                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
           8: areturn       
    }
    Compiled from "TestString.java"
    public class TestString {
      public TestString();
        Code:
           0: aload_0       
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return        
    
      public java.lang.String testString(java.lang.String, java.lang.String);
        Code:
           0: new           #2                  // class java/lang/StringBuilder
           3: dup           
           4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
           7: aload_1       
           8: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          11: aload_2       
          12: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          15: invokevirtual #5                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          18: areturn       
    
      public java.lang.String testStringBuffer(java.lang.StringBuffer, java.lang.String);
        Code:
           0: aload_1       
           1: aload_2       
           2: invokevirtual #6                  // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
           5: invokevirtual #7                  // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
           8: areturn       
    
      public java.lang.String testStringBuilder(java.lang.StringBuilder, java.lang.String);
        Code:
           0: aload_1       
           1: aload_2       
           2: invokevirtual #4                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
           5: invokevirtual #5                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
           8: areturn       
    }
    

    可以看到对于定义String类型的字符做字符连接时所执行的方法要多于StringBuffer和StringBuilder,StringBuffer和StringBuilder是一样的,不过StringBuilder是非线程安全的,StringBuffer是线程安全的,所以一般推荐使用StringBuffer,以前看人用java代码测试运行时间来观察执行效率,其实这样通过字节码大体猜测感觉也是挺不错的。


    展开全文
  • 我们把C代码编译java字节码,这样我们的C语言便具备了可跨品台属性。通过把C语言编译java字节码,我们不但能够继续学习和掌握编译原理相关的算法技术,于此同时,还能深入理解java虚拟机的基本原理,此乃...
  • public class StringDemo1 { public static void main(String[] args) { String str1 = "aaa" + "bbb"; System.out.println(str1); String str2 = "ccc"; str2 += "ddd"; System.out.println(str2);...}
  • 在上一篇文章《StringBuilder、StringBuffer与Java字符串处理》中,我们反汇编了Java字节码文件,通过查看编译器编译后的字节命令,我们能够更清楚地了解Java在字符串拼接等方面的处理机制。 那么,我们如何反...
  • java编译学习字节码指令(一)

    千次阅读 2018-07-31 21:51:35
    最近沉迷于java编译,经常看到iconst_1指令,见得多了,也让我...做个小测试,从11到0,看看它们分别对应字节码什么? public class Bytecode { public void ByteCode() { int eleven = 11; int ten = 10; ...
  • Java编译、反编译、查看字节码

    千次阅读 2012-02-13 23:27:20
    编译:javac -g Foo.java ...看字节码:javap -c -s -l -verbose Foo 反编译Java: 1)下载jad.exe(http://www.varaneckas.com/jad)+eclipse插件jadclipse(http://sourceforge.net/projects/jadclipse/)
  • javap 反编译Java class字节码

    万次阅读 2016-08-04 13:32:41
    javap是Java class文件分解器,可以反编译,也可以查看java编译器生成的字节码。用于分解class文件。  javap.exe位于C:\Program Files\Java\jdk1.8.0_91\bin。由于在环境变量 -> Path 中设置了%JAVA_HOME%\bin的...
  • public class HelloWorld { public int firstStack() { int a = 100; int b = 100; int c = 100; return (a+b)*c; } }
  • 所谓动态编译,就是在程序运行时产生java类,并编译成class文件。 在D盘test目录下有两个java文件:AlTest1.java、AlTest2.java,现需要通过java代码实现java文件到class文件的编译操作: import java.io....
  • Java字节码编译工具 jclasslib

    千次阅读 2018-09-01 18:21:38
    在对 Java 代码执行过程效率分析过程中会很经常查看代码编译后字节码,或者将字节码编译后,查看Java底层对于原来代码的优化结果,当然 JDK 本身已经提供了 javap 反编译工具可以完成这一过程, 也有很多第三方...
  • javap 反编译 java 字节码文件

    千次阅读 2019-11-13 19:17:57
    javap是 Java class文件分解器,可以反编译,也可以查看java编译器生成的字节码,从而对代码内部的执行逻辑进行分析。 语法: 把java文件编译为class文件:javacTest.java(Test.javajava文件名) 生成对应的 ....
  • 怎么用java自带反编译工具查看java字节码文件图文教程,用java自带反编译工具查看java字节码文件,首先,配置好java开发环境,环境变量各种配置,这里就不一一说了,相信大家都配置好了。下面入正题----怎么用java...
  • Java字节码和Dalvik字节码

    千次阅读 2017-06-17 00:03:37
    Java字节码和Dalvik字节码的区别,生成JAVA字节码和Dalvik字节码
  • 下面详细了解JIT编译;从官方JDK中的HotSpot虚拟机的JIT编译器入手,先介绍解释器与JIT编译器是如何配合工作的,认识JIT编译器C1/C2;再看看JIT编译的是什么,以及触发条件是什么;...最后对比Java与C/C++的编译器。
  • 使用javap反编译Java字节码文件

    千次阅读 2016-06-23 09:17:31
    我们如何反编译指定的Java字节码文件呢?其实,在Sun公司提供的JDK中,就已经内置了Java字节码文件反编译工具javap.exe(位于JDK安装目录的bin文件夹下)。 我们可以在dos窗口中使用javap来反汇编指定的Java字节码...
  • javap反编译java字节码文件

    千次阅读 2016-08-16 14:58:17
     javap是 Java class文件分解器,可以反编译,也可以查看java编译器生成的字节码,从而对代码内部的执行逻辑进行分析。 语法:  把java文件编译为class文件:javac Test.java (Test.javajava文件名) 生成对应...
  • 今天在学习jsp生命周期的时候想查看经过Tomcat编译后字节码文件的位置发现在Tomcat的路径中只有最初自带的那些工程,在查找资料发现我们可以在eclipse的工作空间找到这些文件 在你的工作空间路径加 \....
  • Java字节码与Dalvik字节码

    千次阅读 2016-07-13 12:40:46
    JVM只与字节码关联,而不与Java语言直接关联。事实上,JRuby,Groovy等语言也可以由相应的编译器编译字节码,然后由JVM解释执行。甚至可以自己写一个class字节码文件,然后由JVM来执行。 2. class类文件结构 任何...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 233,323
精华内容 93,329
关键字:

java编译后产生字节码

java 订阅