class文件_class文件结构 - CSDN
精华内容
参与话题
  • Java .class文件是什么?

    千次阅读 2015-06-09 17:22:24
    Java class文件是什么?   java class 文件是对Java程序二进制文件格式的精确定义。每一个Java class文件都对一个Java类或者Java接口做出了全面描述。一个class文件中只能包含一个类或者接口。无论Java class...

    Java class文件是什么?

     

    java class 文件是对Java程序二进制文件格式的精确定义。每一个Java class文件都对一个Java类或者Java接口做出了全面描述。一个class文件中只能包含一个类或者接口。无论Java class文件在何种系统上产生,无论虚拟机在何种操作系统上运行,对Java class文件的精确定义使得所有Java虚拟机都能够正确地读取和解释所有Java class文件。

    尽管class文件与java语言结构相关,但它并不一定必须与Java程序相关。

    如上图所示:可以使用其他语言来编写程序,然后将其编译为class文件,或者把Java程序编译为另一种不同的二进制文件格式。实际上,Java class文件的形式能够表示Java源代码中无法表达的有效程序,然而,绝大多数Java开发者几乎都会选择使用class文件作为传递给虚拟机的首要方式。Java class文件是八位字节的二进制流。数据项按顺序存储在class文件中,相邻的项之间没有任何间隔,这样可以使得class文件紧凑。占据多个字节空间的想按照高位在前的顺序分为几个连续的字节存放。

    和java的类可以包含多个不同的字段、方法、方法参数、局部变量等一样,Java class文件也能够包含许多不同大小的项。在class文件中,可变长度项的大小和长度位于其实际数据执之前。这个特性使得class文件流可以从头到尾被顺序解析,首先读出项的大小,然后读出项的数据。

     

     

    Class文件的内容是什么?

     

    Java class文件中包含了Java虚拟机所需知道的,关于类或接口的所有信息。

     

    A class file consists of a single ClassFile structure:

     

    ClassFile {

    u4 magic;

    u2 minor_version;

    u2 major_version;

    u2 constant_pool_count;

    cp_info constant_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];

    }

     

    备注:u1 a single unsigned byte 

     u2 two unsigned bytes 

     u4 four unsigned bytes 

     u8 eight unsigned bytes 

     

    • Magic Number: 0xCAFEBABE
    • Version of Class File Format: the minor and major versions of the class file
    • Constant Pool: Pool of constants for the class
    • Access Flags: for example whether the class is abstract, static, etc
    • This Class: The name of the current class
    • Super Class: The name of the super class
    • Interfaces: Any interfaces in the class
    • Fields: Any fields in the class
    • Methods: Any methods in the class
    • Attributes: Any attributes of the class (for example the name of the sourcefile, etc)

    ClassFile表中各项简介如下:

    (1) magic(魔数)

    每个Java class文件的钱四个字节被称为他的魔数(magic number):0xCAFEBABE。魔数的做作用在于。可以轻松的分辨出Java class文件和非Java class文件,如果一个文件不是以0xCAFEBABE开头,那它肯定不是java class文件。文件格式定义者能够自由选择魔数,前途是这个选中的魔数值没有被广泛应用。当java还被称为“Oak”的时候,这个魔数就已经确定下来了。依照Patrick Naughton(最初Java开发小组的关键成员)的说法:“早在Java第一次作为该语言的名字发布前,我们就寻找一些好玩的、唯一的、容易记忆的东西。选择0xCAFEBABE只不过是一个巧合,他象征着著名的咖啡品牌Peet's Coffee中最受欢迎的baristsa(一种咖啡的名称),他预示着Java这个名字的出现”

     

    (2) minor_version和major_version

    class文件的下面4个字节包含了主、次版本号。随着Java技术的发展,Java class文件格式可能会加入新特性。class文件格式一旦发生变化,版本号也会随之变化。对于Java虚拟机来说,版本号确定了特定的class文件格式,通常只有给定主版本号和一系列次版本号后,Java虚拟机才能够读取class文件。如果class文件的版本号超出了Java虚拟机所能处理的有效范围,Java虚拟机将不会处理该class文件。

    在Sun的JDK1.0.2发布版中,Java虚拟经济实现支持从45.0(主版本号为45,次版本号为0)到45.3的class文件格式。在所有JDK1.1发布版本中虚拟机都能够支持版本从450.到45.65535的class文件格式。在Sun的1.2版本的SDK中,虚拟机能够支持从版本45.0到46.0的class文件格式。

    。。。。。

     

    (3)constant_pool_count和constant_pool

    在class文件中,魔数和版本号后面的是常量池。在前面JVM的介绍中提到的http://boy00fly.iteye.com/blog/1095263,常量池中包含了与文件中类和接口相关的常量。常量池中存储了诸如文字字符串、final变量值、类名和方法名的常量。Java虚拟机把常量池组织为入口列表的形式。在实际列表constant_pool之前,是入口在列表中的计数constant_pool_count.

    常量池中的许多入口都指向其他的常量池入口,而class文件中紧随这常量池的许多条目也会指向常量池中的入口。在整个class文件中,指示常量池入口在常量池列表中位置的整数索引都指向这些这些常量池入口。列表中的第一项索引值为1,第二项索引值为2,一次类推。尽管constant_pool列表中没有索引值为0的入口,但缺失的这一入口也被constant_pool_count计数在内。例如:当constant_pool中有14项(索引值从1到14)时,constant_pool_count的值为15。

    每个常量池入口都从一个长度为一个字节的标志开始,这个标志指出了列表中该位置的常量类型。一旦java虚拟机获取并解析这个标志,Java虚拟机就会知道标志后的常量类型时什么。

     

     

    Entry Type  Tag Value  Description

    CONSTANT_Utf8  A UTF-8 encoded Unicode string 

    CONSTANT_Integer          3  An int literal value 

    CONSTANT_Float  A float literal value 

    CONSTANT_Long  A long literal value 

    CONSTANT_Double  A double literal value 

    CONSTANT_Class  A symbolic reference to a class or interface 

    CONSTANT_String  8 A String literal value 

    CONSTANT_Fieldref                  9            A symbolic reference to a field 

    CONSTANT_Methodref         10  A symbolic reference to a method declared in a class 

    CONSTANT_InterfaceMethodref         11         A symbolic reference to a method declared in an interface 

    CONSTANT_NameAndType         12  Part of a symbolic reference to a field or method 

     

     

    上面的每一个标志都有一个相对应的表,表明通过在标志名后加上"_info"后最来产生。例如:对应于CONSTANT_Class标志的表名为CONSTANT_Class_info,表名为CONSTANT_Utf8_info的表中存储着Unicode字符串的压缩形式。对应于各种不同的常量池入口的表,在后续章节中再阐述。

    在动态连接的Java程序中,常量池充当了十分重要的角色。除了字面常量(或者说直接量)以外,常量池还可以容纳下面几种符号引用;

    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符

    字段是类或接口的实例变量或者类变量。字段的描述符是一个指示字段的类型的字符串。

    方法的描述符也是一个字符串,该字符串指示方法的返回值和参数的数量、顺序和类型。

    在运行时,Java虚拟机使用常量池的全限定名、方法和字段的描述符,把当前类或接口中的代码与其他类或接口中的代码连接起来。

    由于class文件并不包含其内部组件最终内存布局的信息,因此类、字段和方法并不能被class文件中的字节码直接引用。

    java虚拟机从常量池获得符号引用,然后再运行时解析引用项的实际地址。例如,用来调用方法的字节码指令把一个符号引用的常量池所有传给所调用的方法。

     

    (4) access_flags

    紧接常量池后的两个字节称为access_flags,他展示了文件中定义的类或接口的几段信息。例如,访问标志指明文件中丁定义的是类还是接口;访问表示还定义了在类或接口的声明中使用了哪种修饰符;类和接口是抽象的,还是公共的;类的类型可以为final,而final类不可能是抽象的;接口不能为final类型。

    (5) this_class

    接下来的两个字节为this_class项,他是一个对常量池的索引。在this_class位置的常量池入口必须为CONSTANT_Class_info表。.....

     

    (6) super_class

    在class文件中,紧接在this_class之后的是super_class项,他是一个两个字节的常量池索引。在super_class位置的常量池入口是一个指向该类超类全限定名CONSTANT_Class_info.

     

    (7) interface_count和interface

    紧接着super_class的是interface_count。此项的含义为:在文件中由该类直接实现或者由接口扩展的父接口的数量。在这个计数的后面,是名为interface的数组,他包含了对每个由该类或者接口直接实现的父接口的常量池索引。

     

    (8) fields_count和fields

    紧接在interfaces后面的是对在该类或接口中所声明的字段的描述。首先是名为fields_count的计数,他是类变量和实例变量的字段的数量总和。

    在这个技术后面的是不同长度的field_info表的序列。

    field的结构具有以下格式:

     

    field_info {

    u2 access_flags;

    u2 name_index;

    u2 descriptor_index;

    u2 attributes_count;

    attribute_info attributes[attributes_count];

    }

     

     

    (9) methods_count和methods

    紧接在fields后面的是对在该类或接口中所声明的方法的描述。

    method的结构具有以下格式:

     

    method_info {

    u2 access_flags;

    u2 name_index;

    u2 descriptor_index;

    u2 attributes_count;

    attribute_info attributes[attributes_count];

    }

     

     

    (10) attributes_count和attributes

    class文件中最后的部分是属性(attribute),他给出了在该文件中类或接口锁定义的属性的基本信息。

    attribute的结构具有以下格式:

     

    attribute_info {

    u2 attribute_name_index;

    u4 attribute_length;

    u1 info[attribute_length];

    }

    展开全文
  • Class文件

    千次阅读 2018-08-25 03:04:20
    无关性 不同平台的Java虚拟机与所有平台都使用统一的程序存储...Java虚拟机不和包括Java在内的任何语言绑定,它只与Class文件关联,Class文件中包含了Java虚拟机指令集和符号表以及其他辅助信息,虚拟机并不关心C...

    无关性

    不同平台的Java虚拟机与所有平台都使用统一的程序存储格式字节码(ByteCode)来实现Java的平台无关性。此外,在Java虚拟机上也开发出了许多语言,包括Clojure、Groovy、JRuby、Jython、Scala等。Java虚拟机不和包括Java在内的任何语言绑定,它只与Class文件关联,Class文件中包含了Java虚拟机指令集和符号表以及其他辅助信息,虚拟机并不关心Class来源于何种语言。

    Class文件结构

    Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据。当遇到需要占用8位字节以上空间的数据项时,会按照高位在前的方式分割成若干个8位字节进行存储。Class文件采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型:无符号数和表。

    • 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表一个字节、两个字节、四个字节和八个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
    • 表是由多个无符号数或者其他表作为数据项构成的符合数据类型,所有表都以“_info”结尾,表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。表内数据描述类的所有属性,数据项顺序、数量和字节序(Big-Endian)都是严格限定的。

    魔数与Class文件的版本

    每个Class文件开头的u4称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。Class文件的魔数为0xCAFEBABE。紧接着魔数的4个字节存储的是Class文件版本号,第5和第6是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version),虚拟机不会执行超过其版本号的Class文件,但向下兼容。

    常量池

    主版本号之后是常量池入口,常量池是Class文件的资源仓库,它是Class文件结构中与其它项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。常量池入口有一个u2记录常量池容量,容量为该数减一。常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量包括文本字符串、final常量等。符号引用包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

    Java代码在编译时,虚拟机加载Class文件进行动态连接,虚拟机运行时,要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址中。常量池中每一项常量都是一个表,表开始的第一位u1是标志位,代表常量属于哪种类型。

    使用jdk bin目录的javap可以查看Class文件字节码内容,下图是一个类的字节码,可以清楚的看到版本号、常量池等信息。

    这里写图片描述

    访问标志(access_flags)

    常量池之后是两个字节的访问标志,这个标志用于识别一些类或者接口层次的访问信息,包括这个类是类还是接口(ACC_INTERFACE)、是否是public(ACC_PUBLIC)等。如下图所示(ACC_SUPER在jdk1.0.2之后都为真):
    这里写图片描述

    类索引(this_class)、父类索引(super_class)与接口索引集合(interfaces)

    类索引和父类索引是两个u2,接口索引集合是一个u2的集合,类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,因为可以实现多个接口,这些接口从左到右排列在接口集合中,都排在访问标志之后。

    类索引和父类索引指向一个类型为CONSTANT_Class_info的类描述符常量,通过类描述符常量的索引值找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串。接口索引集合第一项为接口计数器。

    字段(field_info)表集合

    字段表集合用于描述接口或类中声明的变量,字段包括类级变量及实例级变量,但不包括在方法内部声明的局部变量。字段包括的信息有:字段作用域、可变性、数据类型等。字段表的具体结构是:access_flags、name_index、descriptor_index、attributes_count、attributes。

    方法表集合

    方法表的结构和字段表一样,方法里的代码存放在“Code”属性表里面,如果没有覆盖父类方法,就不会有父类额方法信息。要重载一个方法,要有与原方法不同的特征签名,代码的特征签名是一个方法中各个参数在常量池中的字段符合引用的集合。因为返回值不包含在特征签名中,所以无法只靠返回值不同来让函数重载。字节码的特征签名还包括方法返回值和受查异常表,所以如果两个函数名称和特征签名相同,返回值不同,也可以存在于一个Class文件中,Java代码里就不能了。

    属性表(attribute_info)集合

    Class文件、字段表、方法表都可以携带自己的属性表集合,用于描述某些场景专有的信息。

    属性表中包括Code、ConstantValue、LocalVariableTable等。下图为一个方法表中的属性表:
    这里写图片描述

    没有参数的方法的args_size=1,这是因为编译器把对this关键字的访问转换为对一个普通方法参数的访问,因此至少有一个指向当前对象的局部变量,只对示例方法有效,如果是static方法就为0。


    异常表会为try…catch..finally生成三条异常记录,分别对应三种情况:

    • 如果try语句块出现属于Exception或其子类的异常,转到catch语句块处理;
    • 如果try语句块出现不属于Exception或其异常的子类,转到finally语句块处理;
    • 如果catch语句块未出现任何异常,转到finally语句块处理。
    展开全文
  • 查看.class文件的各种形式及含义

    千次阅读 2018-10-04 11:52:44
    :%!xxd

    查看16进制:

    public class ByteCode {
    
        private int m;
    
        public int inc() {
            return m + 1;
        }
    
    }
    
    javac ByteCode.java
    vim ByteCode.class
    :%!xxd
    
    00000000: cafe babe 0000 0034 0013 0a00 0400 0f09  .......4........
    00000010: 0003 0010 0700 1107 0012 0100 016d 0100  .............m..
    00000020: 0149 0100 063c 696e 6974 3e01 0003 2829  .I...<init>...()
    00000030: 5601 0004 436f 6465 0100 0f4c 696e 654e  V...Code...LineN
    00000040: 756d 6265 7254 6162 6c65 0100 0369 6e63  umberTable...inc
    00000050: 0100 0328 2949 0100 0a53 6f75 7263 6546  ...()I...SourceF
    00000060: 696c 6501 000d 4279 7465 436f 6465 2e6a  ile...ByteCode.j
    00000070: 6176 610c 0007 0008 0c00 0500 0601 001b  ava.............
    00000080: 636f 6d2f 6578 616d 706c 652f 6465 6d6f  com/example/demo
    

    各字节含义:按顺序(u1 u2 u4 u8分别代表1 2 4 8字节,其他是表类型)
    [图片来源:深入理解java虚拟机 第2版]
    在这里插入图片描述
    计算得出 更直观展示各字节含义:

    javap -verbose ByteCode.class
    
    Classfile xxxx/ByteCode.class
      Last modified 2018-9-30; size 292 bytes
      MD5 checksum 775471577fc46c33efff4361644724aa
      Compiled from "ByteCode.java"
    public class com.example.demoms.ByteCode
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
       #2 = Fieldref           #3.#16         // com/example/demoms/ByteCode.m:I
       #3 = Class              #17            // com/example/demoms/ByteCode
       #4 = Class              #18            // java/lang/Object
       #5 = Utf8               m
       #6 = Utf8               I
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               inc
      #12 = Utf8               ()I
      #13 = Utf8               SourceFile
      #14 = Utf8               ByteCode.java
      #15 = NameAndType        #7:#8          // "<init>":()V
      #16 = NameAndType        #5:#6          // m:I
      #17 = Utf8               com/example/demoms/ByteCode
      #18 = Utf8               java/lang/Object
    {
      public com.example.demoms.ByteCode();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 9: 0
    
      public int inc();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: getfield      #2                  // Field m:I
             4: iconst_1
             5: iadd
             6: ireturn
          LineNumberTable:
            line 14: 0
    }
    SourceFile: "ByteCode.java"
    

    查看字节码指令:(jvm指令)

    javap -c ByteCode.class
    
    public class com.example.demoms.ByteCode {
      public com.example.demoms.ByteCode();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public int inc();
        Code:
           0: aload_0
           1: getfield      #2                  // Field m:I
           4: iconst_1
           5: iadd
           6: ireturn
    }
    

    查看反汇编指令:

    /**
     *JVM参数输出反汇编
     * -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
     */
    public class ByteCode {
    
        private int m;
    
        public int inc() {
            return m + 1;
        }
    
        public static void main(String[] args) {
    
        }
    
    }
    
    

    反汇编输出的代码有点多…部分如下:

    Loaded disassembler from hsdis-amd64.dylib
    Decoding compiled method 0x000000010e198610:
    Code:
    [Disassembling for mach='i386:x86-64']
    [Entry Point]
    [Constants]
      # {method} {0x00000001070e6480} '<init>' '()V' in 'java/lang/Object'
      #           [sp+0x20]  (sp of caller)
      0x000000010e198760: mov    0x8(%rsi),%r10d
      0x000000010e198764: shl    $0x3,%r10
      0x000000010e198768: cmp    %r10,%rax
      0x000000010e19876b: jne    0x000000010e0d8e60  ;   {runtime_call}
      0x000000010e198771: data32 xchg %ax,%ax
      0x000000010e198774: nopl   0x0(%rax,%rax,1)
      0x000000010e19877c: data32 data32 xchg %ax,%ax
    [Verified Entry Point]
      0x000000010e198780: mov    %eax,-0x14000(%rsp)
      0x000000010e198787: push   %rbp
      0x000000010e198788: sub    $0x10,%rsp         ;*synchronization entry
                                                    ; - java.lang.Object::<init>@-1 (line 37)
    
      0x000000010e19878c: mov    0x8(%rsi),%r11d
      0x000000010e198790: movabs $0x0,%r10
      0x000000010e19879a: lea    (%r10,%r11,8),%r10
      0x000000010e19879e: mov    $0x40000000,%r8d
      0x000000010e1987a4: test   %r8d,0x9c(%r10)
      0x000000010e1987ab: je     0x000000010e1987b8  ;*return
                                                    ; - java.lang.Object::<init>@0 (line 37)
    
      0x000000010e1987ad: mov    %rsi,%rbp
      0x000000010e1987b0: data32 xchg %ax,%ax
      0x000000010e1987b3: callq  0x000000010e104960  ; OopMap{rbp=Oop off=88}
                                                    ;*return
                                                    ; - java.lang.Object::<init>@0 (line 37)
                                                    ;   {runtime_call}
      0x000000010e1987b8: add    $0x10,%rsp
      0x000000010e1987bc: pop    %rbp
      0x000000010e1987bd: test   %eax,-0xd7a47c3(%rip)        # 0x00000001009f4000
                                                    ;   {poll_return}
      0x000000010e1987c3: retq                      ;*return
                                                    ; - java.lang.Object::<init>@0 (line 37)
    
      0x000000010e1987c4: mov    %rax,%rsi
      0x000000010e1987c7: add    $0x10,%rsp
      0x000000010e1987cb: pop    %rbp
      0x000000010e1987cc: jmpq   0x000000010e104760  ;   {runtime_call}
      0x000000010e1987d1: hlt    
      0x000000010e1987d2: hlt    
      0x000000010e1987d3: hlt    
      0x000000010e1987d4: hlt    
      0x000000010e1987d5: hlt    
      0x000000010e1987d6: hlt    
      0x000000010e1987d7: hlt    
      0x000000010e1987d8: hlt    
      0x000000010e1987d9: hlt    
      0x000000010e1987da: hlt    
      0x000000010e1987db: hlt    
      0x000000010e1987dc: hlt    
      0x000000010e1987dd: hlt    
      0x000000010e1987de: hlt    
      0x000000010e1987df: hlt    
    [Exception Handler]
    [Stub Code]
      0x000000010e1987e0: jmpq   0x000000010e1017e0  ;   {no_reloc}
    [Deopt Handler Code]
      0x000000010e1987e5: callq  0x000000010e1987ea
      0x000000010e1987ea: subq   $0x5,(%rsp)
      0x000000010e1987ef: jmpq   0x000000010e0da500  ;   {runtime_call}
      0x000000010e1987f4: hlt    
      0x000000010e1987f5: hlt    
      0x000000010e1987f6: hlt    
      0x000000010e1987f7: hlt    
    Decoding compiled method 0x000000010e1988d0:
    Code:
    [Entry Point]
    [Verified Entry Point]
    

    当然也可以使用一些其他的反编译插件

    展开全文
  • Java中的.class文件详解

    万次阅读 2018-05-19 16:24:36
    转载:https://dzone.com/articles/introduction-to-java-bytecode 即使对于有经验的Java开发人员来说,阅读已编译的Java字节码也很乏味。为什么我们首先需要了解这种低级别的东西?这是上周发生在我身上的一个简单...

    转载:https://dzone.com/articles/introduction-to-java-bytecode

            即使对于有经验的Java开发人员来说,阅读已编译的Java字节码也很乏味。为什么我们首先需要了解这种低级别的东西?这是上周发生在我身上的一个简单情况:很久以前,我在机器上进行了一些代码更改,编译了一个JAR,并将其部署到服务器上,以测试性能问题的潜在修补程序。不幸的是,代码从未被检入到版本控制系统中,并且出于某种原因,本地更改被删除而没有追踪。几个月后,我再次需要源代码形式的变化(这需要付出相当大的努力),但是我找不到它们!

            幸运的是编译后的代码仍然存在于该远程服务器上。于是松了一口气,我再次抓取JAR并使用反编译器编辑器打开它......只有一个问题:反编译器GUI并不是一个完美的工具,而且出于某种原因,该JAR中的许多类我想要反编译的特定类在我打开它时在UI中导致了一个错误,并且反编译器崩溃!

            绝望的时代需要绝望的措施。幸运的是,我熟悉原始字节码,我宁愿花些时间手动反编译代码的某些部分,而不是通过修改并再次测试它们。由于我仍然记得至少在代码中查找的地方,因此阅读字节码可帮助我确定确切的更改并将其构造回源代码形式。(我一定要从我的错误中吸取教训,并保留这些时间!)

            关于字节码的好处是你只学习一次它的语法,然后  它适用于所有Java支持的平台  - 因为它是代码中间表示,而不是底层CPU的实际可执行代码。而且,字节码比原生机器码简单,因为JVM架构相当简单,因此简化了指令集。还有一件好事就是,这套系列中的所有指令都由Oracle 完整记录

    在了解字节码指令集之前,让我们先熟悉一些有关作为先决条件所需的JVM的信息。


    JVM数据类型

            Java是静态类型的,它会影响字节码指令的设计,使得指令期望自己能够对特定类型的值进行操作。例如,有一些附加说明添加两个数字:iaddladdfadddadd他们期望类型的操作数分别为int,long,float和double。大部分字节码具有根据操作数类型具有不同形式的相同功能的特性。

    JVM定义的数据类型是:

    1. 原始类型:
      • 数字类型:byte(8位2的补码),short(16位2的补码),int(32位2的补码),long(64位2的补码),char(16位无符号的Unicode),float(32位IEEE 754单元精密FP),double(64位IEEE 754双精度F​​P)
      • boolean 类型
      • returnAddress:指向指令的指针
    2. 参考类型:
      • 类的类型
      • 数组类型
      • 接口类型

            该boolean类型在字节码中的支持有限。例如,没有直接在boolean值上运行的指令布尔值被int编译器转换,并使用相应的int指令。

    Java开发人员应该熟悉以上所有类型,除了returnAddress没有等效的编程语言类型。


    基于堆栈的体系结构

            字节码指令集的简单性很大程度上归功于Sun设计了基于堆栈的VM架构,而不是基于寄存器的架构。JVM进程使用各种内存组件,但只有JVM堆栈需要仔细检查,以便能够遵循字节码指令:

    PC寄存器:对于在Java程序中运行的每个线程,PC寄存器存储当前指令的地址。

    JVM堆栈:对于每个线程,都会分配一个堆栈以存储局部变量,方法参数和返回值。这里是一个显示3个线程堆栈的插图。

    jvm_stacks

    堆:所有线程共享的内存和存储对象(类实例和数组)。对象释放由垃圾收集器管理。

    heap.png

    方法区域:对于每个加载的类,它存储方法代码和符号表(例如对字段或方法的引用)以及称为常量池的常量。

    method_area.png

    JVM堆栈由框架组成,  当方法调用完成后,每个框架都会压入堆栈,并在堆栈中弹出(通过正常返回或抛出异常)。每个框架还包括:

    1. 一个局部变量数组,索引从0到其长度减1.该长度由编译器计算。局部变量可以保存任何类型的值,除了longdouble值,它们占据两个局部变量。
    2. 一个操作数堆栈,用于存储可充当指令操作数的中间值,或将参数推入方法调用。

    stack_frame_zoom.png

    Bytecode Explored

            有了关于JVM内部的一个想法,我们可以看一些从示例代码生成的基本字节码示例。Java类文件中的每个方法都有一个代码段,它由一系列指令组成,每个指令具有以下格式:

    opcode (1 byte)      operand1 (optional)      operand2 (optional)      ...

    这是一个由单字节操作码和零个或多个包含要操作的数据的操作数组成的指令。

            在当前正在执行的方法的堆栈框架内,指令可以将值推送或弹出到操作数堆栈上,并且它可以将值加载或存储在数组本地变量中。我们来看一个简单的例子:

    public  static  void  main(String [] args){
        int  a  =  1 ;
        int  b  =  2 ;
        int  c  =  a  +  b ;
    }

            为了在编译的类中打印生成的字节码(假设它在文件中Test.class),我们可以运行该javap工具:

    javap -v Test.class

    我们得到:

    public static void main(java.lang.String []);
    描述符:([Ljava / lang / String;)V
    标志:(0x0009)ACC_PUBLIC,ACC_STATIC
    码:
    stack = 2,locals = 4,args_size = 1
    0:iconst_1
    1:istore_1
    2:iconst_2
    3:istore_2
    4:iload_1
    5:iload_2
    6:iadd
    7:istore_3
    8:返回
    ...

            我们可以看到方法的方法签名main,一个描述符,指示该方法接受一个Strings([Ljava/lang/String;数组,并且具有void返回类型(V)。随后的一组标志将public(ACC_PUBLIC)和static(ACC_STATIC描述为方法

            最重要的部分是Code属性,该属性包含方法的说明以及操作数堆栈的最大深度(本例中为2)以及此方法的帧中分配的局部变量的数量(4 in这个案例)。在上面的指令中引用了所有局部变量,除了第一个(在索引0处),它保存对args参数的引用其他3个局部变量对应于变量abc在源代码中。

    地址0到8的指令将执行以下操作:

    iconst_1:将整数常量1推到操作数栈上。

    iconst_1.png

    istore_1:弹出顶部操作数(一个int值)并将其存储在索引为1的局部变量中,该变量对应于变量a

    istore_1.png

    iconst_2:将整数常量2推入操作数堆栈。

    iconst_2.png

    istore_2:弹出顶部操作数int值,并将其存储在索引为2的局部变量中,该变量对应于变量b

    istore_2.png

    iload_1:从索引为1的本地变量中加载int值并将其推入操作数堆栈。

    iload_1.png

    iload_2:从索引1处的本地变量加载int值并将其推入操作数堆栈。

    iload_2.png

    iadd:从操作数栈中弹出两个int值,将它们相加,然后将结果推回操作数堆栈。

    我加

    istore_3:弹出顶部操作数int值并将其存储在索引为3的局部变量中,该变量对应于变量c

    istore_3.png

    return:从void方法返回。

    上述每条指令都只包含一个操作码,该操作码完全规定了JVM要执行的操作。

    方法调用

            在上面的例子中,只有一个方法,即主要方法。假设我们需要对变量的值进行更详细的计算c,并且我们决定将它放在一个名为calc

    public  static  void  main(String [] args){
        int  a  =  1 ;
        int  b  =  2 ;
        int  c  =  calc(a,b);
    }
    static  int  calc(int  a,int  b){
        return (int)Math.sqrt(Math.pow(a,2)+ Math.pow(b,2));
    }

    我们来看看生成的字节码:

    public static void main(java.lang.String[]);
      descriptor: ([Ljava/lang/String;)V
      flags: (0x0009) ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=4, args_size=1
           0: iconst_1
           1: istore_1
           2: iconst_2
           3: istore_2
           4: iload_1
           5: iload_2
           6: invokestatic  #2         // Method calc:(II)I
           9: istore_3
          10: return
    static int calc(int, int);
      descriptor: (II)I
      flags: (0x0008) ACC_STATIC
      Code:
        stack=6, locals=2, args_size=2
           0: iload_0
           1: i2d
           2: ldc2_w        #3         // double 2.0d
           5: invokestatic  #5         // Method java/lang/Math.pow:(DD)D
           8: iload_1
           9: i2d
          10: ldc2_w        #3         // double 2.0d
          13: invokestatic  #5         // Method java/lang/Math.pow:(DD)D
          16: dadd
          17: invokestatic  #6         // Method java/lang/Math.sqrt:(D)D
          20: d2i
          21: ireturn

            主要方法代码的唯一区别就是不用iadd指令了,我们现在invokestatic只需调用静态方法calc关键要注意的是操作数堆栈包含传递给方法的两个参数calc换句话说,调用方法通过按照正确的顺序将它们推到操作数堆栈上来准备待调用方法的所有参数。invokestatic(或者类似的调用指令,将在后面看到)将随后弹出这些参数,并为参数放置在其局部变量数组中的被调用方法创建一个新框架。

            我们还注意到,invokestatic通过查看从6跳到9的地址,指令占用3个字节。这是因为,与迄今为止所看到的所有指令不同,它invokestatic包括两个额外的字节来构造对要调用的方法的引用(另外到操作码)。该引用由javap as显示#2,它是对该calc方法的符号引用,从前面介绍的常量池中解析。

            其他新信息显然是该calc方法本身的代码它首先将第一个整数参数加载到操作数堆栈(iload_0)中。下一条指令  i2d通过应用加宽转换将其转换为double。所得到的double替换操作数堆栈的顶部。

            下一条指令将一个双常数2.0d  (从常量池中取出)推送到操作数栈中。然后使用Math.pow到目前为止准备的两个操作数值(第一个参数calc 和常量2.0d来调用静态方法Math.pow方法返回时,其结果将存储在其调用者的操作数堆栈中。这可以在下面说明。

    math_pow.png

    应用相同的过程来计算Math.pow(b, 2)

    math_pow2.png

            下一条指令  dadd弹出前两个中间结果,并添加它们,并将总和推回顶端。最后,invokestatic调用Math.sqrt结果总和,并使用缩小转换(d2i将结果从double转换为int 生成的int返回到main方法,该方法将其存储回cistore_3)。

    实例创作

    我们来修改示例并引入一个类Point来封装XY坐标。

    public class Test {
        public static void main(String[] args) {
            Point a = new Point(1, 1);
            Point b = new Point(5, 3);
            int c = a.area(b);
        }
    }
    class Point {
        int x, y;
        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
        public int area(Point b) {
            int length = Math.abs(b.y - this.y);
            int width = Math.abs(b.x - this.x);
            return length * width;
        }
    }
    main方法的编译字节码如下所示:
    public static void main(java.lang.String[]);
      descriptor: ([Ljava/lang/String;)V
      flags: (0x0009) ACC_PUBLIC, ACC_STATIC
      Code:
        stack=4, locals=4, args_size=1
           0: new           #2       // class test/Point
           3: dup
           4: iconst_1
           5: iconst_1
           6: invokespecial #3       // Method test/Point."<init>":(II)V
           9: astore_1
          10: new           #2       // class test/Point
          13: dup
          14: iconst_5
          15: iconst_3
          16: invokespecial #3       // Method test/Point."<init>":(II)V
          19: astore_2
          20: aload_1
          21: aload_2
          22: invokevirtual #4       // Method test/Point.area:(Ltest/Point;)I
          25: istore_3
          26: return

            这里encountereted新的指令newdupinvokespecial与编程语言中的新运算符类似,该new指令创建一个在传递给它的操作数中指定类型的对象(这是对该类的符号引用Point)。对象的内存分配在堆上,并且对该对象的引用被压入操作数堆栈。

            该dup指令复制前操作数堆栈值,这意味着现在我们有两个引用Point在堆栈的顶部对象。接下来的三条指令将构造函数的参数(用于初始化对象)推送到操作数堆栈中,然后调用与构造函数相对应的特殊初始化方法。下一个方法是字段xy将被初始化的地方。该方法完成后,前三个操作数堆栈值将被消耗,剩下的是对创建对象的原始引用(到目前为止,已成功初始化)。

    init.png

    接下来,  astore_1弹出Point引用并将其分配给索引为1的局部变量(ain astore_1表示这是参考值)。

    init_store.png

    重复创建和初始化Point分配给变量的第二个实例的相同过程b

    init2.png

    init_store2.png

            最后一步从索引1和2的本地变量(分别使用aload_1和)aload_2分别加载对两个Point对象的引用,并调用area使用方法invokevirtual,该方法根据对象的实际类型来处理调用的适当方法。例如,如果变量a包含一个SpecialPoint扩展类型的实例Point,并且子类型覆盖该area方法,则调用overriden方法。在这种情况下,没有子类,因此只有一种area方法可用。

    area.png

            请注意,即使该area方法接受一个参数,堆栈顶部仍有两个Point引用。第一个(pointA来自变量a)实际上是调用该方法的实例(this在编程语言中也被称为),并且将在该area方法的新帧的第一个局部变量中传递另一个操作数值(pointB)是该area方法的参数

    结论

            由于字节码指令集的简单性以及在生成指令时几乎没有编译器优化,拆分类文件可能是一种检查应用程序代码变化的方法,在没有源代码时,这种方法可以尝试一下。




    展开全文
  • 深入理解JVM之Java字节码(.class文件详解

    万次阅读 多人点赞 2018-01-04 11:52:41
    Understanding bytecode makes you a better programmer ...因此,本文从class字节码文件的结构入手,一步步来解剖二进制字节码的内部工作原理,这对深入理解JVM的运行机制大有裨益,同时,对于想要使
  • Java Class文件格式简析

    千次阅读 2018-07-04 20:41:17
    Java开发只需要编写Java代码之后通过javac命令将其编译成.class文件,.class文件可以被JVM虚拟机加载并执行。如果需要Java能够像动态语言那样编码,通常需要修改.class文件的内容,这种情况下了解.class文件的内部...
  • Class文件详解

    千次阅读 2018-05-27 20:33:53
    引言 众所周知,Java语言有一个很重要的特点是平台无关性,即用Java语言编写的程序可以在不同平台之间无缝迁移,Java对这个特性有一个著名的宣传口号:“一次编写,到处运行(Write Once,Run AnyWhere)”。...
  • Java虚拟机中定义的Class文件格式。每一个Class文件都对应着唯一一个类或接口的定义信息,但是相对地,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。我们只是通俗地将任意一个有效...
  • 深入理解Java Class文件格式(一)

    万次阅读 多人点赞 2014-03-25 17:35:11
    原因很简单, JVM不会理解我们写的Java源文件, 我们必须把Java源文件编译成class文件, 才能被JVM识别, 对于JVM而言, class文件相当于一个接口, 理解了这个接口, 能帮助我们更好的理解JVM的行为;另一方面, ...
  • eclipse编译的class文件在哪里

    万次阅读 2017-11-08 11:39:52
    在eclipse中查找编译后的class
  • 怎样加Java文件编译成class文件

    万次阅读 2018-06-25 10:26:29
    1.打开cmd,找到jdk安装的bin目录下,如图2,然后找到要编译的Java文件,我的Java文件在E:\workspaces\portal_one\JUnit_Test\src\main\java\com\baidu\test\dome\loader\Test.java;使用命令为Javac E:\workspaces...
  • 反编译class文件,一种最简单方法

    万次阅读 2019-04-23 19:54:19
    今天突然需要反编译一下以前的一个class文件,确定一下是否与源码一种,在网上找了一些方法,发现比较复杂。 自己无意间发现的一种简单方法,分享给大家。 就是把class文件,拖拽到IntelliJ IDEA工具中,IntelliJ...
  • Eclipse 编译java项目不能生成class文件

    万次阅读 2014-09-12 18:27:37
    我原来认为只要把build下的class文件都删除,重启运行一下就可以了,但是运行了几遍,仍旧不能生成class文件,也不知系统运行过程中执行的代码是从什么地方获取的,难道都缓存到虚拟机里了吗?然后google了一下,...
  • java在线反编译class文件

    万次阅读 多人点赞 2020-04-02 10:58:41
    反编译网址:http://javare.cn 对你有帮助的话,右上角给个赞呗~
  • 如何将.java文件编译成.class文件

    万次阅读 2015-03-24 20:15:43
    首先确定电脑的jdk环境变量配置完成,然后打开DOS命令操作符界面。本文假设文件为Lader.java,存储在F盘下。...2.输入javac Lader.java 打开F盘,会看到生成的Lader.class文件 3.输入Java Lader 
  • 紧急关头难免会替换class文件,但一定要注意一下几点。  1、小心class内中的project 的类的后,替换文件需要加上 $classMapper这个类, 编译成class的时候,会把public class 中的protected class 编译为$.class , ...
  • IDEA不生成.class文件

    万次阅读 2018-01-29 10:46:04
    IDEA不生成.class文件 JDK版本问题,查看以下配置是否与已安装的版本一致 这个地方莫名其妙就变成未指定状态了,害我找半天 IDEA指定输出路径 IDEA左侧Project列表不显示out文件夹
  • 线上项目替换class文件的陷阱

    万次阅读 2015-04-23 13:57:43
    小心class内中的project 的类的后,替换文件需要加上 $classMapper这个类, 编译成class的时候,会把public class 中的protected class 编译为$..class , 所以替换的时候也要找到这个class替换上, 不过最好是打成war包...
  • 学习JVM时,难免要对class文件进行研究.Oracle为我们提供了专门分析Class文件字节码的工具:javap,因此我们只需把该工具集成到我们的IDEA中就可以查看class文件,并将内容打印在IDE的console中查看。 配置步骤如下: ...
  • 这篇博客总结一下关于Eclipse的java项目中没有class文件以及有class文件却看不见的解决方法,这些方法自然有它的局限性,希望读者能根据自己的实际情况来做出选择。看不到class文件 这里的解决办法很简单,eclipse...
1 2 3 4 5 ... 20
收藏数 3,203,252
精华内容 1,281,300
关键字:

class文件