JAVA 字节码文件分析

本文详细探讨了Java字节码文件的结构,包括魔数、版本号、常量池、访问标志、类信息、成员变量、方法信息等关键组成部分。通过对字节码文件的深入分析,揭示了JVM如何将字节码翻译成可执行的机器码,阐述了字节码在跨平台执行中的作用。此外,文章还讨论了字节码的执行过程、动态分派机制以及动态代理的字节码实现,加深了对Java字节码的理解。
摘要由CSDN通过智能技术生成

​​​​Java不只是一种编程语言,还是一个完整的操作平台。Java之所以可以跨平台,这离不开JVM虚拟机

JVM是一个软件,在不同的平台上,JVM有不同的版本。Java在编译之后会生成一种.class文件,这种文件成为字节码文件。JVM虚拟机就是将Java编译后的.class文件翻译成特定平台下的机器码,然后运行。也就是说,在不同平台上装上平台对应的JVM虚拟机后,就可以将Java字节码文件转换,然后运行我们的Java程序。

值得注意的是,Java编译后的结果是生成字节码,而不是机器码。字节码是不可以直接运行的,必须通过JVM再次翻译成机器码才可以运行。即使是将Java程序打包成可执行文件,也仍然需要JVM的支持才可以运行。

跨平台的是Java程序,而不是JVM。JVM是用C/C++开发的,不能平台,不同的平台下JVM的版本是不同的。

字节码文件,有什么用?

  1. JVM虚拟机的特点:一处编译,多处运行。

  2. 多处运行,靠的是.class 字节码文件。

  3. JVM本身,并不是跨平台的。Java之所以跨平台,是因为JVM本身不夸平台。

  4. 二进制的文件,显然不是给人看的。是给机器看的。

  5. 从根源了解了之后,返回到语言层次 好多都会豁然开朗。

Java语言规范补充:

JVM虚拟机规范(相对底层的)Java,Groovy,kotlin,Scala。 编译后都是Class文件,所以就都能在JVM虚拟机上运行。

 

字节码文件解读

一个Java类,然后进行编译成字节码文件

package com.dawa.jvm.bytecode; 
public class MyTest1 { 
    private int a = 1; 
    public int getA() { return a; } 
    public void setA(int a) { this.a = a; } 
}

javap 编译后的结果:

➜  main javap com.dawa.jvm.bytecode.MyTest1   
Compiled from "MyTest1.java"
public class com.dawa.jvm.bytecode.MyTest1 {
  public com.dawa.jvm.bytecode.MyTest1();
  public int getA();
  public void setA(int);
}

Java -c 编译后的结果:

➜  main javap -c com.dawa.jvm.bytecode.MyTest1 
Compiled from "MyTest1.java"
public class com.dawa.jvm.bytecode.MyTest1 {
  public com.dawa.jvm.bytecode.MyTest1();
    Code:
       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

  public int getA();
    Code:
       0: aload_0
       1: getfield      #2                  // Field a:I
       4: ireturn

  public void setA(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      #2                  // Field a:I
       5: return
}

javap -verbose编译后的结果

 ➜   main javap -verbose com.dawa.jvm.bytecode.MyTest1 
Classfile /Users/shangyifeng/work/workspace/jvm_leature/build/classes/java/main/com/dawa/jvm/bytecode/MyTest1.class
  Last modified 2020-2-14; size 489 bytes
  MD5 checksum 952635139a8b5b42f0142d033929d8c2
  Compiled from "MyTest1.java"
public class com.dawa.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         // com/dawa/jvm/bytecode/MyTest1.a:I
   #3 = Class              #22            // com/dawa/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               Lcom/dawa/jvm/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               com/dawa/jvm/bytecode/MyTest1
  #23 = Utf8               java/lang/Object
{
  public com.dawa.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 3: 0
        line 4: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/dawa/jvm/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 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/dawa/jvm/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 11: 0
        line 12: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lcom/dawa/jvm/bytecode/MyTest1;
            0       6     1     a   I
}
SourceFile: "MyTest1.java"

字节码整体结构分解

  • 整体结构

     

  1. 魔数
  2. 版本号
  3. 常量池数-1
  4. 常量池数组(常量池中的每个常量的具体信息)
  5. 当前类的访问控制权限(private,public , pro , 等几种 当前类的标识符)
  6. 当前类的名字
  7. 父类的名字
  8. 当前类的接口信息
  9. 当前类的成员变量的信息
  10. 当前类的方法的信息
  11. 当前类附加的属性

上面一张图说明了字节码所有的事情

image-20200214213356594

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

常量池深入分析

借助工具:Hex_fiend(mac) 查看16进制的文件

用工具打开的二进制文件:16进制

4字节展示格式

4byte展示

单字节展示格式

单字节16进制的class文件

使用javap -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、类中的方法信息、类变量与成员变量等信息。

  • 魔数:所有的.class字节码文件的前4个字节都是魔数,魔术值为固定值:0xCAFEBABE
  • 版本号:魔术之后的4个字节为jdk版本信息,前两个表示此版本号,后两个表示主版本号,这里的00 00 00 34,换成十进制,表示此版本为0,主版本号为52,该文件的版本号为 1.8.0
  • 常量池:紧接着主版本号之后的就是常量池,一个java类中定义的很多信息 都是常量池来维护和描述的,可以将常量池看作class文件的资源仓库,比如说java类中定义的方法与变量信息,都是存储在常量池中,常量池中主要存储两类常量:字面量与符号引用,字面量如文本字符串,java中声明为final的常量值等。而符号引用如类和接口的全局限定名,字段的名称和描述符、方法的名称和描述符等。常量池:里面的值不一定都是常量。变量也是放在常量池的。
  • 常量池的总体结构:java类所对应的常量池主要由常量池数量常量池数组这两部分 共同构成。常量池数量紧跟在主版本号后面,占据2个字节,常量池数组紧跟在常量池数量之后,常量池数组与一般的数组不同,常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同;但是 每一种元素的第一个数据都是u1类型,该字节是标志位,占据1个字节。jvm在解析常量池时,会根据这个u1类型来获取元素的具体类型。
  • 在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;对于数组类型来说,int[] 记录为[I,String[][] 记录为[[Ljava/lang/String
  • 用描述符描述方法时,按照先参数列表,后返回值的顺序来描述。参数列表按照参数的严格顺序放在一组()之内,如方法 String getDetail(int id,String name)的描述为(I,Ljava/lang/String)Ljava/lang/String
  • 下面我们解析一下字节码文件
  1.  16进制 00 18 就是24 ,代表常量池数组长度24,  但是 常量池数组中元素个数 = 常量池数 -1 (其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池”的含义:根本愿意在于 索引为0也是一个常量,只不过它不位于常量表中,对应的就是null,所以 常量池的索引从1开始。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值