Java字节码分析快速入门/字节码执行分析(一)

目录

什么是字节码?

为什么要了解字节码?

如何查看字节码?

字节码包括哪些内容?

总结


        hello读者盆友们,在上一篇文章[Java基础]面向对象-内存解析_小王师傅66的博客-CSDN博客最后,我们通过查看字节码,来印证字符串拼接底层调用StringBuilder.append()方法实现。下面这篇文章,将重点学习Java字节码相关的内容,欢迎大家点评。

什么是字节码?

       我们知道,计算机直接使用的程序语言语句是机器指令码,又称机器码/代码。机器码是用于指挥计算机执行操作和操作数地址的一组二进制数,只用0和1表示的数。开始人们用机器码编写程序,就是机器语言。机器码计算机能理解,但和人的语言差异太大,不便于人理解和记忆,使用机器码编程出错率高。后来,人们用助记符代替机器码编程,就是汇编语言。再后来,出现了各种更便于人理解的高级语言,如C,C++,Java等,使人们不用了解计算机的指令系统和具体结构也能编程。虽然语言逐渐向便于人类理解的方向发展,但计算机只能理解机器码,所以在执行时,汇编语言和高级语言都需要解释和翻译成机器码才能在计算机上执行。了解了机器码的发展基础之后,我们再来看字节码。

       在前面的文章【Java基础】Java总览_小王师傅66的博客-CSDN博客我们讲到过,Java语言非常重要的特点:Write Once,Run  Any Where  一次编译,到处执行。这个特点体现在,SUN公司以及虚拟机供应商发布了许多可以运行在不同平台的虚拟机,这些虚拟机可以载入和执行同一种字节码,而与平台无关。而且不仅Java语言,其他语言编译成字节码文件,也能在Java虚拟机载入和执行,虚拟机并不关心由什么语言编译成的字节码文件。

(图来自《深入理解Java虚拟机》) 
(图来自《深入理解Java虚拟机》) 

        Java程序经过Javac编译器,编译成字节码文件 ,后缀是.class

为什么要了解字节码?

1. 可以读懂Java代码的执行过程。

     通过查看字节码,我们可以真正理解Java代码的执行流程,理解方法调用的过程,理解异常处理的过程等。这有助于我们写出高质量的Java代码。

2. 可以做Java语言层面的优化。

     通过分析字节码,我们可以找到Java代码的瓶颈,并对其进行优化。比如可以通过调整方法参数顺序避免不必要的对象创建,可以选择更高效的数据结构和算法等。

3. 可以研究Java虚拟机。

     Java字节码是Java虚拟机执行的指令,通过学习字节码,我们可以更好的理解Java虚拟机的工作原理,甚至可以研究虚拟机的优化技术。

4. 可以编写字节码操作框架。

     有很多框架需要操作字节码,比如ASM框架,Javassist框架等。如果我们要使用这些框架,就必须要了解Java字节码。

5. 有助于学习其他JVM语言。

       很多JVM语言最后也是编译成字节码执行,所以学习Java字节码也有助于我们学习Scala、Kotlin、Groovy等JVM语言。

6. 安全性方面也很重要。

        很多Java安全机制都是基于字节码实现的,比如沙箱安全模型等。了解字节码可以帮助我们更好的理解Java的安全机制,写出更安全的代码。

如何查看字节码?

1. javap命令。

        javap是Java自带的可以反编译class文件查看字节码的命令。我们可以通过`javap -c ClassName` 或 `javap -v ClassName`来查看类的字节码。

注:- javap -c 只输出每个方法的字节码指令,不包含任何注释和变量表信息。这适用于仅仅分析方法的执行逻辑。- javap -v 除了输出方法的字节码指令外,还会输出方法的注释信息,变量表,异常表等详细信息。使用这个命令可以更全面地分析一个类,理解它的设计和实现。

2. IDE插件。

        很多Java IDE都有查看字节码的插件,比如IDEA有jclasslib插件,Eclipse有Bytecode Outline插件。使用这些插件可以直接在IDE中查看字节码。

3. 反编译工具。

        比如CFR、Procyon等反编译工具,可以反编译class文件为Java代码,通过查看生成的Java代码可以间接分析字节码。

4. 字节码分析框架。

        有些框架可以直接分析字节码,生成报告。比如ASM Bytecode Outline插件,可以将字节码转换为htm报告,方便分析。

5. JVM TI工具。

        JVM TI(JVM Tool Interface)工具可以在运行时分析JVM的内部状态,通过这些工具也可以查看和分析字节码。常见的工具有jvisualvm,YourKit等。

注:1,2,4亲测有效,安装文档很多,大家可自行百度适合自己的

字节码包括哪些内容?

我们接着用上一篇文章中的例子进行分析[Java基础]面向对象-内存解析_小王师傅66的博客-CSDN博客这篇文章,将为大家讲解,Java代码执行过程中,内存空间占用的情况,图文并茂,值得一看。https://blog.csdn.net/WKX18330698534/article/details/131127876

代码:

package selfTest;

/**
 * 类 名 称:Circle
 * 类 描 述:
 * 创建时间:2023/6/7 9:25 上午
 * 创 建 人:admin
 */
public class Circle {
    private CirclePoint o;
    private double radius;

    public CirclePoint getO() {
        return o;
    }

    public void setO(double i, double j) {
        o.setX(i);
        o.setY(j);
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public double getArea() {
        return 3.14 * radius * radius;
    }

    // 无参构造函数
    Circle(CirclePoint o, double r) {
        this.o = o;
        this.radius = r;
    }

    // 自定义初始值的构造函数
    Circle(double r) {
        o = new CirclePoint(0, 0);
        radius = r;
    }

    Circle() {
    }
}

class CirclePoint {
    private double x;
    private double y;

    CirclePoint() {
    }

    CirclePoint(double x1, double y1) {
        this.x = x1;
        this.y = y1;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }
}

class TestCircle {
    public static void main(String[] args) {
        Circle c1 = new Circle(new CirclePoint(1.0, 2.0), 2.0);
        Circle c2 = new Circle(5.0);

        System.out.println("c1:(" + c1.getO().getX() + "," + c1.getO().getY() + ")," + c1.getRadius());
        System.out.println("c2 area = " + c2.getArea());
        double newI = 5d;
        double newJ = 6d;
        c1.setO(newI, newJ);
        c2.setRadius(9.0);

    }

}

我们用命令分析一下: javap -v TestCircle.class 

Classfile /Users/admin/Projects/testProject/target/classes/selfTest/TestCircle.class
  Last modified 2023-6-20; size 1329 bytes
  MD5 checksum 058d64231223967f4d87092a0eb77dfe
  Compiled from "Circle.java"
class selfTest.TestCircle
  minor version: 0  // 次版本号
  major version: 52 // 主版本号
  flags: ACC_SUPER
Constant pool:   // 常量池
    #1 = Methodref          #34.#54       // java/lang/Object."<init>":()V
    #2 = Class              #55           // selfTest/Circle
    #3 = Class              #56           // selfTest/CirclePoint
    #4 = Double             2.0d
    #6 = Methodref          #3.#57        // selfTest/CirclePoint."<init>":(DD)V
    #7 = Methodref          #2.#58        // selfTest/Circle."<init>":(LselfTest/CirclePoint;D)V
    #8 = Double             5.0d
   #10 = Methodref          #2.#59        // selfTest/Circle."<init>":(D)V
   #11 = Fieldref           #60.#61       // java/lang/System.out:Ljava/io/PrintStream;
   #12 = Class              #62           // java/lang/StringBuilder
   #13 = Methodref          #12.#54       // java/lang/StringBuilder."<init>":()V
   #14 = String             #63           // c1:(
   #15 = Methodref          #12.#64       // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #16 = Methodref          #2.#65        // selfTest/Circle.getO:()LselfTest/CirclePoint;
   #17 = Methodref          #3.#66        // selfTest/CirclePoint.getX:()D
   #18 = Methodref          #12.#67       // java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;
   #19 = String             #68           // ,
   #20 = Methodref          #3.#69        // selfTest/CirclePoint.getY:()D
   #21 = String             #70           // ),
   #22 = Methodref          #2.#71        // selfTest/Circle.getRadius:()D
   #23 = Methodref          #12.#72       // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #24 = Methodref          #73.#74       // java/io/PrintStream.println:(Ljava/lang/String;)V
   #25 = String             #75           // c2 area =
   #26 = Methodref          #2.#76        // selfTest/Circle.getArea:()D
   #27 = Double             6.0d
   #29 = Methodref          #2.#77        // selfTest/Circle.setO:(DD)V
   #30 = Double             9.0d
   #32 = Methodref          #2.#78        // selfTest/Circle.setRadius:(D)V
   #33 = Class              #79           // selfTest/TestCircle
   #34 = Class              #80           // java/lang/Object
   #35 = Utf8               <init>
   #36 = Utf8               ()V
   #37 = Utf8               Code
   #38 = Utf8               LineNumberTable
   #39 = Utf8               LocalVariableTable
   #40 = Utf8               this
   #41 = Utf8               LselfTest/TestCircle;
   #42 = Utf8               main
   #43 = Utf8               ([Ljava/lang/String;)V
   #44 = Utf8               args
   #45 = Utf8               [Ljava/lang/String;
   #46 = Utf8               c1
   #47 = Utf8               LselfTest/Circle;
   #48 = Utf8               c2
   #49 = Utf8               newI
   #50 = Utf8               D
   #51 = Utf8               newJ
   #52 = Utf8               SourceFile
   #53 = Utf8               Circle.java
   #54 = NameAndType        #35:#36       // "<init>":()V
   #55 = Utf8               selfTest/Circle
   #56 = Utf8               selfTest/CirclePoint
   #57 = NameAndType        #35:#81       // "<init>":(DD)V
   #58 = NameAndType        #35:#82       // "<init>":(LselfTest/CirclePoint;D)V
   #59 = NameAndType        #35:#83       // "<init>":(D)V
   #60 = Class              #84           // java/lang/System
   #61 = NameAndType        #85:#86       // out:Ljava/io/PrintStream;
   #62 = Utf8               java/lang/StringBuilder
   #63 = Utf8               c1:(
   #64 = NameAndType        #87:#88       // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #65 = NameAndType        #89:#90       // getO:()LselfTest/CirclePoint;
   #66 = NameAndType        #91:#92       // getX:()D
   #67 = NameAndType        #87:#93       // append:(D)Ljava/lang/StringBuilder;
   #68 = Utf8               ,
   #69 = NameAndType        #94:#92       // getY:()D
   #70 = Utf8               ),
   #71 = NameAndType        #95:#92       // getRadius:()D
   #72 = NameAndType        #96:#97       // toString:()Ljava/lang/String;
   #73 = Class              #98           // java/io/PrintStream
   #74 = NameAndType        #99:#100      // println:(Ljava/lang/String;)V
   #75 = Utf8               c2 area =
   #76 = NameAndType        #101:#92      // getArea:()D
   #77 = NameAndType        #102:#81      // setO:(DD)V
   #78 = NameAndType        #103:#83      // setRadius:(D)V
   #79 = Utf8               selfTest/TestCircle
   #80 = Utf8               java/lang/Object
   #81 = Utf8               (DD)V
   #82 = Utf8               (LselfTest/CirclePoint;D)V
   #83 = Utf8               (D)V
   #84 = Utf8               java/lang/System
   #85 = Utf8               out
   #86 = Utf8               Ljava/io/PrintStream;
   #87 = Utf8               append
   #88 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
   #89 = Utf8               getO
   #90 = Utf8               ()LselfTest/CirclePoint;
   #91 = Utf8               getX
   #92 = Utf8               ()D
   #93 = Utf8               (D)Ljava/lang/StringBuilder;
   #94 = Utf8               getY
   #95 = Utf8               getRadius
   #96 = Utf8               toString
   #97 = Utf8               ()Ljava/lang/String;
   #98 = Utf8               java/io/PrintStream
   #99 = Utf8               println
  #100 = Utf8               (Ljava/lang/String;)V
  #101 = Utf8               getArea
  #102 = Utf8               setO
  #103 = Utf8               setRadius
{
  selfTest.TestCircle();
    descriptor: ()V
    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 79: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LselfTest/TestCircle;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=8, locals=7, args_size=1
         0: new           #2                  // class selfTest/Circle  ---创建Circle对象
         3: dup                               // ---复制栈顶元素
         4: new           #3                  // class selfTest/CirclePoint  ---创建CirclePoint对象
         7: dup                               // ---复制栈顶元素
         8: dconst_1                          // ---将double类型常量1入栈(将1.0入栈)
         9: ldc2_w        #4                  // double 2.0d   ---将常量池中的double类型的项入栈
        12: invokespecial #6                  // Method selfTest/CirclePoint."<init>":(DD)V     ---调用CirclePoint的构造函数
        15: ldc2_w        #4                  // double 2.0d   ---将常量池中的double类型的项入栈
        18: invokespecial #7                  // Method selfTest/Circle."<init>":(LselfTest/CirclePoint;D)V   ---调用Circle的构造函数
        21: astore_1                          // ---将引用类型或returnAddress类型值存入局部变量表1(将c1存入局部变量表)
        22: new           #2                  // class selfTest/Circle
        25: dup                               // ---复制栈顶元素    
        26: ldc2_w        #8                  // double 5.0d  ---将常量池中的double类型的项入栈
        29: invokespecial #10                 // Method selfTest/Circle."<init>":(D)V    ---调用Circle的构造函数
        32: astore_2                          // ---将引用类型或returnAddress类型值存入局部变量表2(将c2存入局部变量表)

        33: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;    ---从类中获取静态字段
        36: new           #12                 // class java/lang/StringBuilder   ---创建StringBuilder对象
        39: dup                               // ---复制栈顶元素
        40: invokespecial #13                 // Method java/lang/StringBuilder."<init>":()V      ---调用StringBuilder的构造函数
        43: ldc           #14                 // String c1:(              --- 将String型常量"c1:("从运行时常量池push到栈顶
        45: invokevirtual #15                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;    ---调用StringBuilder的成员函数append()
        48: aload_1                           // 从局部变量1中装载引用类型值到栈顶(对应第21行局部变量表中的c1)
        49: invokevirtual #16                 // Method selfTest/Circle.getO:()LselfTest/CirclePoint;    --- 调用Circle的成员函数getO()
        52: invokevirtual #17                 // Method selfTest/CirclePoint.getX:()D      --- 调用CirclePoint的成员函数getX()
        55: invokevirtual #18                 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;     ---调用StringBuilder的成员函数append()
        58: ldc           #19                 // String ,                --- 将String型常量","从运行时常量池push到栈顶
        60: invokevirtual #15                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;     ---调用StringBuilder的成员函数append()
        63: aload_1                           // 从局部变量1中装载引用类型值到栈顶(对应第21行局部变量表中的c1)
        64: invokevirtual #16                 // Method selfTest/Circle.getO:()LselfTest/CirclePoint;   --- 调用Circle的成员函数getO()
        67: invokevirtual #20                 // Method selfTest/CirclePoint.getY:()D      --- 调用CirclePoint的成员函数getY()
        70: invokevirtual #18                 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;      ---调用StringBuilder的成员函数append()
        73: ldc           #21                 // String ),                --- 将String型常量"), "从运行时常量池push到栈顶
        75: invokevirtual #15                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        ---调用StringBuilder的成员函数append()
        78: aload_1                           // 从局部变量1中装载引用类型值到栈顶(对应第21行局部变量表中的c1)
        79: invokevirtual #22                 // Method selfTest/Circle.getRadius:()D      --- 调用Circle的成员函数getRadius()
        82: invokevirtual #18                 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;          ---调用StringBuilder的成员函数append()
        85: invokevirtual #23                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;         ---调用StringBuilder的成员函数toString()
        88: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V         ---调用PrintStream的成员函数println()
        91: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
        94: new           #12                 // class java/lang/StringBuilder
        97: dup
        98: invokespecial #13                 // Method java/lang/StringBuilder."<init>":()V
       101: ldc           #25                 // String c2 area =
       103: invokevirtual #15                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       106: aload_2
       107: invokevirtual #26                 // Method selfTest/Circle.getArea:()D
       110: invokevirtual #18                 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;
       113: invokevirtual #23                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       116: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       119: ldc2_w        #8                  // double 5.0d
       122: dstore_3
       123: ldc2_w        #27                 // double 6.0d
       126: dstore        5
       128: aload_1
       129: dload_3
       130: dload         5
       132: invokevirtual #29                 // Method selfTest/Circle.setO:(DD)V
       135: aload_2
       136: ldc2_w        #30                 // double 9.0d
       139: invokevirtual #32                 // Method selfTest/Circle.setRadius:(D)V
       142: return
      LineNumberTable:
        line 81: 0
        line 82: 22
        line 84: 33
        line 85: 91
        line 86: 119
        line 87: 123
        line 88: 128
        line 89: 135
        line 91: 142
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0     143     0  args   [Ljava/lang/String;
           22     121     1    c1   LselfTest/Circle;
           33     110     2    c2   LselfTest/Circle;
          123      20     3  newI   D
          128      15     5  newJ   D
}
SourceFile: "Circle.java"

反编译后的内容主要包括以下几方面的内容:

1. 魔数和版本号。

        class文件开头的4个字节是魔数(0xCAFEBABE),用于确定该文件是否是一个能被虚拟机接受的文件。

        紧接着是版本号,用于标识这个class文件遵循哪个版本的class文件格式。

2. 常量池。

        常量池中存放了两大类常量:字面量(Literal)和符号引用(Symbolic References)。

        字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值、8种基本类型的值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:类和接口的全限定名(Fully Qualified Name),字段的名称和描述符(Descriptor),方法的名称和描述符。

        比如上面代码的字符串常量“c1:(” 就是字面量;

        类和接口的全限定名:selfTest/CirclePoint;

        字段的名称和描述符:c1  c2;

        方法的名称和描述符: selfTest/Circle.getArea:();

        上面的反编译结果中可以看出,计算机帮我们把103项常量都计算出来了。

3. 访问标志。

        表示该类的访问权限(public、private等)以及是否是抽象类或接口等信息。包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等。

4. 字段表。

        包含类变量以及实例变量信息,如名称、类型和访问权限等。

5. 方法表。

        包含方法信息,如方法名称、返回值类型、参数类型、访问权限、方法体的字节码指令等。

6. 属性表。

        包含类或方法的额外属性,比如Annotation等。

        字段表,方法表,属性表用来描述一些不方便使用“固定字节”表达的内容。譬如描述方法的返回值是什么,有几个参数,每个参数的类型等。因为Java中的“类”是无穷无尽的,无法通过简单的无符号字节来描述一个方法用到了什么类,因此在描述方法的这些信息时,需要引用常量表中的符号引用进行表达。

7. 接口信息。

        对实现的接口进行索引。

8. 字段和方法属性。

        包含字段和方法的附加属性,如方法的参数数量和本地变量表等。

9. 方法体。

        包含方法的字节码指令,行号信息等。

10. 附加属性。

        一些额外的类属性,格式不固定,由属性名和属性值组成。

总结

     以上是字节码相关知识的总览,要想完全掌握字节码的知识,我们更需要了解:栈帧,操作数栈,局部变量表,堆等,掌握了代码在执行过程中如何在这几块区域中执行,就更清楚了。这些内容后面我们将讲到。

        字节码指令的意义,大家可参考官网,多查几次就记住了,不用死记硬背,

Chapter 6. The Java Virtual Machine Instruction Set

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java编程语言已成为互联网应用领域的开发基石,随着移动设备和技术的快速发展,Java也将继续发挥着重要的作用。因此,Java作为一门重要的编程语言,它的入门也是十分重要的。 在Java入门过程中,我们需要了解Java的基本语法和对面向对象编程思想的理解。Java语言是一种类C语言的高级编程语言,很多语法和C语言相似,因此对有基础的程序员来说学习会更快。不过对于零基础的人来讲,需要认真读入门教材并自己动手尝试。 首先,通过编写简单的程序,我们可以开始了解Java的基本语法规则。Java基本的程序结构由类、方法和变量组成。其中,类是Java程序的基本单元,每个程序都必须要有一个类。方法是类中完成特定功能的代码块,我们可以通过调用方法执行特定的功能。而变量则是用于存放数据的临时存储器,可以被程序中其他部分引用和修改。除此之外,Java还具有一些其他的元素,包括注释、关键字和操作符等等。 Java程序的执行流程,与其他一些编程语言类似,包括三个基本步骤:编辑、编译和运行。在Java中,先编写代码,并将其保存到Java文件中(.java文件),然后通过编译器将Java代码转换为字节码(.class文件),最后使用Java虚拟机(JVM)来实际执行程序。这种结构可以在多个平台上运行,使得Java具有较好的跨平台性能。 总的来说,Java入门需要多方面的引导和学习,包括教材、实验、编程工具等等。Java的学习,不仅需要了解语法和基础,还需要实践和创新,通过不断的练习和完善编程技能,不断提高自己工作的效率和质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王师傅66

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值