• Java基础- 浅谈javac和javap


    javac

    javac 是 Java 编程语言的编译器,它是 Java Development Kit (JDK) 的一部分。javac 命令用于将 Java 源代码文件(扩展名为 .java)编译成 Java 字节码文件(扩展名为 .class),这些字节码文件随后可以由 Java 虚拟机 (JVM) 执行。以下是对 javac 指令的详细介绍:

    基本用法

    • 命令格式javac [options] [sourcefiles] [classes] [args]
    • 示例:要编译名为 MyProgram.java 的源文件,使用以下命令:
      javac MyProgram.java
      
      • 1

    常用选项

    • -d 目录:指定编译器输出 .class 文件的目标目录。如果目录不存在,javac 将尝试创建它。
    • -classpath-cp 路径:设置查找类文件和注释处理器的路径。
    • -sourcepath 路径:指定查找输入源文件的位置。
    • -target 版本:指定生成特定版本的 Java VM 的类文件。例如,如果想生成与 Java 8 兼容的类文件,可以使用 -target 1.8
    • -source 版本:提供与 -target 选项相对应的源代码的版本支持。
    • -g:生成所有调试信息,包括局部变量的信息。
    • -Xlint:提供对源代码的更全面检查,报告更多警告和潜在的问题。
    • -encoding 编码:指定源文件使用的字符编码,例如 UTF-8

    高级选项

    • -bootclasspath 路径:覆盖引导类文件的位置。这在与非标准的 Java 运行时环境 (JRE) 一起使用时特别有用。
    • -processor:指定要使用的注释处理器的名称。
    • -J 选项:直接将选项传递给 JVM。

    错误处理

    • 当源代码中有语法或语义错误时,javac 会报告这些错误,并在可能的情况下给出修改建议。
    • 编译错误必须修正才能成功编译程序。

    版本

    • javac 的行为和可用选项可能会根据 JDK 的版本略有不同。建议查看使用的 JDK 版本的官方文档了解具体细节。

    使用场景

    • javac 通常在命令行环境中使用,但也可以在集成开发环境 (IDE) 如 Eclipse 或 IntelliJ IDEA 中间接使用。

    了解 javac 的这些方面对于 Java 开发者来说是很重要的,因为它是 Java 开发过程中的基本工具之一。通过有效地使用 javac,开发者可以确保他们的 Java 程序被正确编译,且在目标 Java 虚拟机上运行无误。

    javap

    javap 是 Java Development Kit (JDK) 中的一个命令行工具,它被用作 Java 类文件的反汇编程序。这意味着可以使用 javap 来查看编译后的 Java 字节码,或者更具体地说,查看 .class 文件中的信息。这对于理解 Java 字节码、调试、以及学习 Java 编译器如何工作非常有帮助。

    基本用法

    • 命令格式javap [options] [classes]
    • 示例:要反汇编名为 MyClass.class 的类文件,使用以下命令:
      javap MyClass
      
      • 1
      注意,不需要指定 .class 扩展名。

    常用选项

    • -c:显示方法的字节码。这是最常用的选项,因为它允许我们看到 Java 源代码是如何被编译成字节码的。
    • -p-private:显示所有类和成员,包括私有的。
    • -v-verbose:提供关于类文件结构的详细信息,包括常量池。
    • -s:显示内部类型签名。
    • -l:输出行号和本地变量表。

    高级选项

    • -classpath-cp 路径:设置查找类文件的路径。
    • -bootclasspath 路径:覆盖引导类文件的位置。
    • -b:反汇编备用(备份)类文件。
    • -sysinfo:显示系统信息(包括 Java 环境属性)。

    输出解析

    • javap 输出的内容包括类的声明、继承的父类、实现的接口、构造函数、方法、字段等。
    • 如果使用 -c 选项,我们将看到每个方法的字节码,这对于理解 Java 字节码非常有帮助。

    使用场景

    • 调试javap 可以帮助理解为什么编译的 Java 代码在运行时表现异常。
    • 学习:通过查看 Java 字节码,了解 Java 编译器是如何将高级语言转换为机器码的。
    • 优化:检查编译器优化结果或理解性能问题。

    注意事项

    • javap 仅提供对字节码的静态分析。它不能告诉我们程序的运行时行为。
    • 对于使用高级功能(如泛型)的代码,反汇编的结果可能不易于理解,因为这些结构在字节码中有不同的表示。

    了解 javap 对于希望深入理解 Java 字节码的开发者和研究者来说是非常重要的。通过 javap,我们可以更好地理解 Java 程序的底层工作原理。

    在 Java 字节码中,方法描述符 descriptor: ([Ljava/lang/String;)V 是用来描述方法签名的一部分。这个描述符具体来说包含两个部分:参数类型和返回类型。

    1. 参数类型([Ljava/lang/String;)

      • [ 表示数组。
      • Ljava/lang/String; 表示 String 类型,其中 L 表示引用类型,java/lang/StringString 类的内部名称,用斜杠 / 而不是点 . 分隔包名和类名。
    2. 返回类型V

      • V 表示 void 类型。在 Java 字节码中,void 类型用 V 来表示。

    因此,([Ljava/lang/String;)V 描述的是一个方法,它接受一个 String 类型数组作为参数(这是 main 方法的标准形式),并且没有返回值(void)。在 Java 源代码中,这对应于如下签名:

    public static void main(String[] args)
    
    • 1

    理解 Java 字节码的方法描述符对于深入理解 Java 虚拟机(JVM)和 Java 字节码的工作原理是很重要的。这种低级表示形式揭示了 Java 源代码是如何被转换为字节码,进而在 JVM 上执行的。

    Java字节码

    将下面这段Java代码编译为class字节码文件,然后反汇编class文件,观察生成的字节码。

    public class Demo1_22 {
        public static void main(String[] args) {
            String s1 = "a";
            String s2 = "b";
            String s3 = "ab";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    将.java编译为.class,然后反汇编.class文件

    javac .\Demo1_22.java
    javap -v .\Demo1_22.class
    
    • 1
    • 2

    反汇编出的内容如下:

    Classfile /D:/ideaProject/JVM_Detect/src/per/mjn/Demo1_22.class
      Last modified 20231118; size 312 bytes
      MD5 checksum 3ae0972c52c7e5643f0a0ba3dccd4758
      Compiled from "Demo1_22.java"
    public class per.mjn.Demo1_22
      minor version: 0
      major version: 55
      flags: (0x0021) ACC_PUBLIC, ACC_SUPER
      this_class: #5                          // per/mjn/Demo1_22
      super_class: #6                         // java/lang/Object
      interfaces: 0, fields: 0, methods: 2, attributes: 1
    Constant pool:
       #1 = Methodref          #6.#15         // java/lang/Object."":()V
       #2 = String             #16            // a
       #3 = String             #17            // b
       #4 = String             #18            // ab
       #5 = Class              #19            // per/mjn/Demo1_22
       #6 = Class              #20            // java/lang/Object
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               main
      #12 = Utf8               ([Ljava/lang/String;)V
      #13 = Utf8               SourceFile
      #14 = Utf8               Demo1_22.java
      #15 = NameAndType        #7:#8          // "":()V
      #16 = Utf8               a
      #17 = Utf8               b
      #18 = Utf8               ab
      #19 = Utf8               per/mjn/Demo1_22
      #20 = Utf8               java/lang/Object
    {
      public per.mjn.Demo1_22();
        descriptor: ()V
        flags: (0x0001) ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."":()V
             4: return
          LineNumberTable:
            line 3: 0
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: (0x0009) ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=4, args_size=1
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: ldc           #4                  // String ab
             8: astore_3
             9: return
          LineNumberTable:
            line 5: 0
            line 6: 3
            line 7: 6
            line 8: 9
    }
    SourceFile: "Demo1_22.java"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    以上代码显示了 per.mjn.Demo1_22 类的详细结构。它包括类的定义、常量池、两个方法(构造函数和 main 方法)以及源文件信息。下面,我们来逐部分进行解释。

    类定义

    • 文件信息

      • Classfile /D:/ideaProject/JVM_Detect/src/per/mjn/Demo1_22.class:类文件的路径。
      • Last modified 2023年11月18日; size 312 bytes:最后修改时间和文件大小。
      • MD5 checksum:文件的 MD5 校验和。
      • Compiled from "Demo1_22.java":源文件名称。
    • 类信息

      • public class per.mjn.Demo1_22:公共类 per.mjn.Demo1_22
      • minor version: 0, major version: 55:类文件格式的版本号,这里是 Java 11(major version 55)。
      • flags: (0x0021) ACC_PUBLIC, ACC_SUPER:类的访问标志,表示这是一个公共类,并使用 super 关键字。
      • this_class: #5, super_class: #6:类自身和父类的引用。
      • interfaces: 0, fields: 0, methods: 2, attributes: 1:类实现的接口数量、字段数量、方法数量和属性数量。

    常量池

    • 列出了类文件中所有的常量引用,包括类名、方法名、字段名、字符串常量等。
    • 例如,#1 = Methodref #6.#15 指向 Object 类的构造函数,#2 = String #16 是字符串 “a” 的引用。

    方法

    1. 构造函数 per.mjn.Demo1_22()

      • public:公共访问级别。
      • descriptor: ()V:无参数,返回 void
      • 字节码指令:调用父类 Object 的构造函数,然后返回。
    2. main 方法 public static void main(java.lang.String[])

      • public static:公共静态方法。
      • descriptor: ([Ljava/lang/String;)V:接受一个字符串数组参数,返回 void
      • 字节码指令:创建三个字符串 “a”、“b” 和 “ab”,分别存储在局部变量中,然后返回。

    行号表

    • 显示源代码行号与字节码指令之间的对应关系。

    源文件信息

    • SourceFile: "Demo1_22.java":指明源文件名。

    这个字节码文件提供了 Demo1_22 类的结构和行为的低级视图。它有助于理解 Java 编译器是如何将源代码转换为字节码的,以及这些字节码是如何在 Java 虚拟机上执行的。这对于深入理解 Java 和 JVM 的工作原理是非常有价值的。


    补充

    在 Java 字节码中,方法描述符 descriptor: ([Ljava/lang/String;)V 是用来描述方法签名的一部分。这个描述符具体来说包含两个部分:参数类型和返回类型。

    1. 参数类型([Ljava/lang/String;)

      • [ 表示数组。
      • Ljava/lang/String; 表示 String 类型,其中 L 表示引用类型,java/lang/StringString 类的内部名称,用斜杠 / 而不是点 . 分隔包名和类名。
    2. 返回类型V

      • V 表示 void 类型。在 Java 字节码中,void 类型用 V 来表示。

    因此,([Ljava/lang/String;)V 描述的是一个方法,它接受一个 String 类型数组作为参数(这是 main 方法的标准形式),并且没有返回值(void)。在 Java 源代码中,这对应于如下签名:

    public static void main(String[] args)
    
    • 1

    理解 Java 字节码的方法描述符对于深入理解 Java 虚拟机(JVM)和 Java 字节码的工作原理是很重要的。这种低级表示形式揭示了 Java 源代码是如何被转换为字节码,进而在 JVM 上执行的。

  • 相关阅读:
    初试Eureka注册中心
    Python:螺旋矩阵与正方形二维列表
    哭了,我终于熬出头了,Java开发4年,费时8个月,入职阿里,涨薪14K
    CI868K01-eA 3BSE048845R2 106M1081-01
    LeetCode 2562. 找出数组的串联值:模拟(双指针)
    SAP UI5 SmartTable 控件的使用介绍试读版
    Spring Boot的配置文件
    html+css+js网页设计 旅游 大理旅游7个页面
    SpringBoot整合Swagger2
    13Java数组与数组内存图
  • 原文地址:https://blog.csdn.net/weixin_43844521/article/details/134485806