深入理解JVM-java字节码文件结构剖析(1)

public class MyTest1 {

    private int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }
}
javap -verbose MyTest1
警告: 二进制文件MyTest1包含jvm.bytecode.MyTest1
Classfile /Users/luozhiyun/Documents/work/jvm_lecture/target/classes/jvm/bytecode/MyTest1.class
  Last modified Mar 14, 2019; size 471 bytes
  MD5 checksum b2dc69fae4f63b54509ddc1a9210e9c3
  Compiled from "MyTest1.java"
public class jvm.bytecode.MyTest1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#21         // jvm/bytecode/MyTest1.a:I
   #3 = Class              #22            // jvm/bytecode/MyTest1
   #4 = Class              #23            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ljvm/bytecode/MyTest1;
  #14 = Utf8               getA
  #15 = Utf8               ()I
  #16 = Utf8               setA
  #17 = Utf8               (I)V
  #18 = Utf8               SourceFile
  #19 = Utf8               MyTest1.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = NameAndType        #5:#6          // a:I
  #22 = Utf8               jvm/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public jvm.bytecode.MyTest1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field a:I
         9: return
      LineNumberTable:
        line 6: 0
        line 8: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Ljvm/bytecode/MyTest1;

  public int getA();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field a:I
         4: ireturn
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/bytecode/MyTest1;

  public void setA(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field a:I
         5: return
      LineNumberTable:
        line 15: 0
        line 16: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Ljvm/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

对应的16进制
cafe babe
魔数:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:OxCAFEBABE

0000 0034
魔数之后的4个字节为版本信息,前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。这里的版本号为00 00 00 34 ,换算成十进制,表示次版本号为0,主版本号为52。所以,该文件的版本号为:1.8.0

0018
表示一共有24个常量 

0a 代表值为10 Methodref_info 这个常量
00 04 00 14 代表4 和 20   即上面反编译的结果 #4.#20 

09 表示Fieldref
0003 0015  表示3 和 21 即上面反编译的结果 #3.#21

07 表示Class
00 16 代表 #22

07 表示Class
00 17 代表 #23

01 表示utf8
00 01 长度为1
61 代表a

01 表示utf8
00 01 长度为1
49 代表I

0c  表示NameAndType
00 07 表示指向该字段或方法名称常量项的索引  #7:
00 08 表示指向该字段或方法描述符常量项的索引  #8

0c
00 05 表示指向该字段或方法名称常量项的索引  #5
00 06 表示指向该字段或方法描述符常量项的索引  #6

Access flag
00 21 表示ACC_PUBLIC 与ACC_SUPER取的并集

This Class Name
00 03 表示的一个索引指的是常量池第三个常量

This Super Name
00 04 表示的一个索引指的是常量池第四个常量

Interfaces
00 00 表示没有实现接口

Fields
00 01 表示属性的数量,表示有一个字段
00 02    access_flag 表示private
00 05 name_index 名字的索引
00 06 descriptor_index 描述符的索引
00 00 表示attributes_count为0,也就没有attributes_info

Method
00 03 代表有三个方法,包含了自动生成的默认构造器
00 01 代表是一个public的方法
00 07 代表是name_index  <init>
00 08 代表是descriptor_index. ()V
00 01 代表是attribute_count,有一个属性
method attribute
00 09 代表属性名的索引attribute_name_index  code
00 00 00 38 attribute_length  56个字节长度
00 02 max_stack
00 01 max_local 局部变量最大值
00 00 00 0a code_length 方法的代码长度 10个字节,代表这个方法所真正运行的字节码
2a b7 00 01 2a 04 b5 00 02 b1
实际上就是对应着下面的助记符
0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield      #2                  // Field a:I
9: return

2a -> aload_0
b7 -> invokespecial
04 -> aload_0
b5 -> putfield (为成员变量设置,通过这个变量我们可以知道,成员变量其实是在构造器中赋值的)
b1 -> return

exception
00 00

attributes
00 02 attribute_count; 表示有两个属性
00 0a 属性的索引 #10  LineNumberTable
00 00 00 0a attribute_length 10个字节
00 02 00 00 00 06 00 04 00 08
00 02 表示两两映射
00 00  -> 00 06 表示偏移量为0映射到行号为6
00 04  -> 00 08 表示偏移量为4映射到行号为8

00 0b 第二个属性的索引 #11 LocalVariableTable
00 00 00 0c 表示LocalVariableTable所占的字节长度
00 01 00 00 00 0a 00 0c 00 0d 00 00
00 01 局部变量的个数
00 00   局部变量开始的位置
00 0a 局部变量结束的位置
00 0c 局部变量索引   #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00 用来做校验检查的

第二个方法
00 01 访问修饰符 代表public
00 0e 方法的名字索引  getA
00 0f   方法的描述符的索引 ()I
00 01 代表是attribute_count,有一个属性
00 09 代表属性名的索引attribute_name_index  code
00 00 00 2f 属性的长度47
00 01 max_stack
00 01 max_local 局部变量的数量是1
00 00 00 05 code_length 方法的代码长度 5个字节
2a b4 00 02 ac 方法的执行体
2a -> aload_0
b4 -> getField.
00 02 表示常量池中的第二个常量 #2
ac -> ireturn 表示返回一个整型

exception
00 00

attributes
00 02 attribute_count; 表示有两个属性
00 0a 索引指向第10个常量 LineNumberTable
00 00 00 06 attribute_length 6个字节
00 01 00 00 00 0b 表示只有一行, 偏移量为零对应行号8
00 0b 第二个属性的索引 #11 LocalVariableTable
00 00 00 0c 表示LocalVariableTable所占的字节长度 12个

00 01 00 00 00 05 00 0c 00 0d 00 00
00 01 局部变量的个数
00 00   局部变量开始的位置
00 05 局部变量结束的位置
00 0c 局部变量索引的位置  #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00 用来做校验检查的

第三个方法
00 01 表示public
00 10 表示索引#16  setA
00 11   表示索引#17  (I)V
00 01 代表是attribute_count,有一个属性
00 09 代表属性名的索引attribute_name_index  code
00 00 00 3e code_length 方法的代码长度 62个字节
00 02 max_stack
00 02 max_local
00 00 00 06 code_length 方法的代码长度 6个字节
2a 1b b5 00 02 b1
2a -> aload_0
1b -> iload_1
b5 -> putfield
00 02 表示第二常量
b1 -> return 

attributes
00 02 attribute_count; 表示有两个属性
00 0a 索引指向第10个常量 LineNumberTable
00 00 00 0a attribute_length 10个字节
00 02 00 00 00 0f 00 05 00 10
00 02 表示有两个对应关系
00 00 00 0f 偏移量为0的对应的是12行
00 05 00 10 偏移量为5的对应的是16行
00 0b 第二个属性的索引 #11 LocalVariableTable
00 00 00 16 表示LocalVariableTable所占的字节长度 22个
00 02 00 00 00 06 00 0c 00 0d 00 00 00 00 00 06 00 05 00 06 00 01
00 02 局部变量的个数
00 00   局部变量开始的位置
00 06 局部变量结束的位置
00 0c 局部变量索引的位置  #12 this
00 0d 索引 #13 Ljvm/bytecode/MyTest1;
00 00   局部变量开始的位置
00 06 局部变量结束的位置
00 05 局部变量索引的位置  #5 a
00 06 索引#6 I

Attributes
00 01
00 12 索引 #18  sourceFile
00 00 00 02 源文件的长度
00 13 索引#19  MyTest1.java

cafe babe 0000 0034 0018 0a00 0400 1409
0003 0015 0700 1607 0017 0100 0161 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 164c 6a76 6d2f 6279
7465 636f 6465 2f4d 7954 6573 7431 3b01
0004 6765 7441 0100 0328 2949 0100 0473
6574 4101 0004 2849 2956 0100 0a53 6f75
7263 6546 696c 6501 000c 4d79 5465 7374
312e 6a61 7661 0c00 0700 080c 0005 0006
0100 146a 766d 2f62 7974 6563 6f64 652f
4d79 5465 7374 3101 0010 6a61 7661 2f6c
616e 672f 4f62 6a65 6374 0021 0003 0004
0000 0001 0002 0005 0006 0000 0003 0001
0007 0008 0001 0009 0000 0038 0002 0001
0000 000a 2ab7 0001 2a04 b500 02b1 0000
0002 000a 0000 000a 0002 0000 0006 0004
0008 000b 0000 000c 0001 0000 000a 000c
000d 0000 0001 000e 000f 0001 0009 0000
002f 0001 0001 0000 0005 2ab4 0002 ac00
0000 0200 0a00 0000 0600 0100 0000 0b00
0b00 0000 0c00 0100 0000 0500 0c00 0d00
0000 0100 1000 1100 0100 0900 0000 3e00
0200 0200 0000 062a 1bb5 0002 b100 0000
0200 0a00 0000 0a00 0200 0000 0f00 0500
1000 0b00 0000 1600 0200 0000 0600 0c00
0d00 0000 0000 0600 0500 0600 0100 0100
1200 0000 0200 13
  1. 使用javap -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、 常量池、类的构造方法、类中的方法信息、类变量与成员变量等信息。
  2. 魔数:所有的.class字节码文件的前4个字节都是魔数,魔数值为固定值:OxCAFEBABE
  3. 魔数之后的4个字节为版本信息,前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。这里的版本号为00 00 00 34 ,换算成十进制,表示次版本号为0,主版本号为52。所以,该文件的版本号为:1.8.0
  4. 常量池(content pool):紧接着主版本号之后的就是常量池入口。一个java类中定义的很多信息都是由常量池来维护和描述的。可以将常量池看坐是Class文件的资源仓库,比如说java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。
  5. 常量池的总体结构:java类所对应的常量池主要由常量池数量与常量池数组这两部分共同构成。常量池数量紧跟在主版本号后面,占据2个字节;常量池数组则紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同;但是,每一种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节。jvm在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数 = 常量池数 -1 (其中0暂时不适用),目的是满足某些常量池索引值的数据在特定情况下需要表达「不引用任何一个常量池」的含义;根本原因在于,索引为0也是一个常量(保留常量),只不过它部位与常量表中,这个常量就对应null值;所以,常量池的索引从1而非0开始。
  6. 在jvm规范中,每个变量/字段都有描述信息,描述信息主要的作用是描述字段的数据类型、方法的参数列表(包含数量、类型与顺序)与返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符来表示,对象类型则使用字符L加对象的全限定名称来表示。为了压缩字节码文件的体积,对于基本数据类型,jvm都只使用一个大写字母来表示,如下所示:B- byte , C - char, D - double, F -float, I - int, J -long, S - short , Z - boolean, V - void, L -对象类型,如Ljava/lang/String;
  7. 对于数组类型来说,每一个维度使用一个前置的[来表示,如int[]被记录为[I,String[][]被记录为[[Ljava/lang/String;
  8. 用描述符来描述方法时,按照先参数列表,后返回值的顺序来描述,参数列表按照参数的严格顺序放在一组()之内,如方法:String getRealnamByIdANdNickName(int id,String name)的描述符为:(I,Ljava/lang/String;)Ljava/lang/String;

Java字节码整体结构

  • 4个字节 Magic Number 魔数值为OxCAFEBABE
  • 2+2个字节 Version 前两个字节表示minor version(次版本号),后两个字节表示major version(主版本号)。1.1(45),1.2(46),1.3(47),1.4(48),1.5(49),1.6(50),1.7(51)
  • 2+ n个字节 Constant Pool 包括字符串常量、数值常量等
  • 2个字节 Access Flags 访问标志(public class 、private class 等)
  • 2个字节 This Class Name
  • 2个字节 Super Class Name
  • 2+n个字节 Interfaces
  • 2+n个字节 Fields 当前这个类的成员变量的信息
  • 2+n个字节 Methods
  • 2+n个字节 Attributes 当前这个类的附加的属性

Class字节码中有两种数据类型
* 字节数据直接量:这事基本的数据类型。共细分为u1、u2、u4、u8四种,分别代表连续的1个字节、2个字节、4个字节、8个字节组成的整体数据
* 表(数组):表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合。表是有结构的,它的结构体现在:组成表的成分所在的位置和顺序都是已经严格定义好的。

ClssFile{
    u4              magic;
    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              method_count;
    method_info     methos[method_count];
    u2              attributes_count;
    attribute_info  attributes[attributes_count];
}

Access_Flag访问标志

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

字段表集合
字段表用于描述类和接口中声明的变量。这里的字段包含了类基表变量以及实例变量,但是不包括方法内部声明的局部变量。

field_info{
    u2              access_flags; 0002
    u2              name_index;  0005
    u2              descriptor_index; 0006
    u2              attributes_count;   0000
    attribute_info  attributes[attributes_count];
}

方法表

前三个字段和field_info一样

method_info{
    u2                  access_flags;
    u2                  name_index;
    u2                  descriptor_index;
    u2                  attributes_count;
    attribute_info      attributes[attributes_count];
}

方法的属性结构
方法中的每个属性都是一个attribute_info结构

attribute_info{
    u2  attribute_name_index;
    u4  attribute_length;
    u1  info[attribute_length];
}
* jvm预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用。
* 不同的attribute通过attribute_name_index来区分

Code结构

Code attribute的作用是保存该方法的结构,如所对应的字节码

Code_attribute{
    u2  attribute_name_index;
    u4  attribute_length;
    u2  max_stack;
    u2  max_locals;
    u4  code_length;
    u1  code[code_length];
    u2  exception_table_length;
    {
        u2  start_pc;
        u2  end_pc;
        u2  handler_pc;
        u2  catch_type;
    }   exception_table[exception_table_length];
    u2  attribute_count;
    attribute_info attributes[attribute_count];
}
* attribute_length表示attribute所包含的字节数,不包含attribute_name_index和attribute_length字段
* max_stack表示这个方法运行的任何时刻所能达到的操作数栈的最大深度
* max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量
* code_length表示该方法所包含的字节码的字节数以及具体的指令码
* 具体字节码即是该方法被调用时,虚拟机所执行的字节码
* exception_table,这里存放的是处理异常的信息
* 每个exception_table表项由start_pc, end_pc,  handler_pc, catch_type组成
* start_pc和end_pc表示在code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理
* handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池的一个异常类。当catch_type为0时,表示处理所有的异常

附加属性

接下在是该方法的附加属性
LineNumberTable:这个属性用来表示code数组中的字节码和java代码行数之间的关系。这个属性可以用来在调试的时候定位代码执行的行数

lineNumberTable_attribute{
    u2 attribute_name_index;
    u4  attribute_length;
    u2  line_number_table_length;
    {
        u2 start_pc
        u2  line_number;
    }line_number_table[line_number_table_length];
}

上面的信息可以用jClasslib这样的一个工具来查看

原文地址:https://www.cnblogs.com/luozhiyun/p/10546608.html

时间: 2024-11-05 19:00:26

深入理解JVM-java字节码文件结构剖析(1)的相关文章

JVM --java 字节码的结构解析

Java字节码文件的主体结构分为一下几个部分:Class文件头部.常量池区域.当前类的描述信息.字段列表.方法列表.属性列表. Class文件头部 任何的class文件的前四个字节的内容就是CA FE BA BE .Java是一种咖啡,故称之为coffee baby.这四个字节作为该文件的校验码.-->4 接下来是JDK的版本信息,两个字节代表JDK的最低版本,两个字节JDK的最高版本.-->4 常量池区域 常量池中每一项包括两部分:一个字节标志他的类型,然后是内容,内容的宽度是不一样的.而这

JVM Java字节码方法表与属性

方法表 methods_count  method_info,前三个字段和field_info一样 2.方法的属性结构 方法中的每个属性都是一个attribut_info结构 JVM定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用. 不同的attribute通过attribute_name_index来区分. 原文地址:https://www.cnblogs.com/linlf03/p/11080389.html

JVM(三):深入分析Java字节码-上

JVM(三):深入分析Java字节码-上 字节码文章分为上下两篇,上篇也就是本文主要讲述class文件存在的意义,以及其带来的益处.并分析其内在构成之一 ---字节码,而下篇则从指令集方面着手,讲解指令集都有哪些,以及其各自代表的含义.最后总结一下Class文件存在的必然性. 意义 前面说过 Java 虚拟机拥有平台无关性,但其实现在语言无关性在 JVM 和更加的体现了出来.表现就是目前越来越多的语言可以在 JVM 上运行,而这背后的逻辑,就是这些语言都会被编译为 Class 文件,然后在JVM

JVM(四):深入分析Java字节码-下

JVM(四):深入分析Java字节码-下 在上文中,我们讲解了 Class 文件中的文件标识,常量池等内容.在本文中,我们就详细说一下剩下的指令集内容,阐述其分别代表了什么含义,以及 JVM 团队这样设计的意义. 简介 JVM 指令设计为仅有一个字节长度,由操作码和紧随其后的零至多个操作数来构成. 这里说到 JVM 的指令仅有一个字节,这意味着 JVM 在操作超过一个字节长度的数据时,需要在运行时重建出多字节数据类型的具体数据结构,例如 Long 等.这会导致这个操作不是原子操作,在高并发的情况

从1+1=2来理解Java字节码从1+1=2来理解Java字节码

编译“1+1”代码 首先我们需要写个简单的小程序,1+1的程序,学习就要从最简单的1+1开始,代码如下: 写好java类文件后,首先执行命令javac TestJava.java 编译类文件,生成TestJava.class. 然后执行反编译命令javap -verbose TestJava,字节码结果显示如下: Compiled from "TestJava.java" public class top.luozhou.test.TestJava minor version: 0 m

JVM之字节码——Class文件格式

如同讲汇编必先讲计算机组成原理,在开始字节码之前,我们先了解一下JVM的主要构成. 在JVM的内部,主要由如下几个部分构成:     1.数据区 方法区:存放类定义信息.字节码.常量等数据,在Sun HotSpot JVM中,这块也称为Perm Gen. 堆:创建的对象信息将放入堆中,堆内部如何实现各虚拟机各不相同,对于Sun HotSpot JVM来说又分为Young Gen和Tenured Gen,更详细描述参见<[Java性能剖析]Sun JVM内存管理和垃圾回收 > Java 栈:对于

这一次,彻底弄懂「Java字节码文件」

提前祝福各位读者??圣诞快乐!这个圣诞节请在学习中度过! 不啰嗦,直接从最最简单的一段Java源代码开启Java整体字节码分析之旅. 1.Java 源码文件 package com.dskj.jvm.bytecode; public class MyTest1 { private int a = 1; public int getA() { return a; } public void setA(int a) { this.a = a; } } 2.Java字节码文件 IDEA工具编译代码后,

深入了解 Java 字节码

1.1 什么是字节码? Java 在刚刚诞生之时曾经提出过一个非常著名的口号: "一次编写,到处运行(write once,run anywhere)",这句话充分表达了软件开发人员对冲破平台界限的渴求."与平台无关"的理想最终实现在操作系统的运用层上: 虚拟机提供商开发了许多可以运行在不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现了程序的"一次编写到处运行". 各种不同平台的虚拟机与所有平台都统一使用的程序存储格

java字节码忍者禁术

Java语言本身是由Java语言规格说明(JLS)所定义的,而Java虚拟机的可执行字节码则是由一个完全独立的标准,即Java虚拟机规格说明(通常也被称为VMSpec)所定义的. JVM字节码是通过javac对Java源代码文件进行编译后生成的,生成的字节码与原本的Java语言存在着很大的不同.比方说,在Java语言中为人熟知的一些高级特性,在编译过程中会被移除,在字节码中完全不见踪影. 这方面最明显的一个例子莫过于Java中的各种循环关键字了(for.while等等),这些关键字在编译过程中会