您的位置 首页 java

「编译引擎」-学习阅读Class文件结构(javap版)

1.前言

在《【编译引擎】学习阅读Class文件结构(16进制版)》中,我们一起直接阅读了Class文件的16进制版本。

虽然这种方式可以帮助我们深刻理解Class文件结构,但如果您从事的是应用软件开发(而不是编译器相关工作),这样就比较低效了。

今天我们就来看看 JVM 提供的javap,如何提升我们解读Class结构的工作效率。

2.javap

2.1.命令行学习方法

最好的学习资源就是官方文档:

在这里,我们可以获得最全面的javap使用指导。

2.2.命令行详解

2.2.1.javap的命令行结构

javap [options] classfiles…

[options]:javap的命令行选项

classfiles:是我们需要反汇编的一个或多个类文件。

2.2.2.最常用的options

实战中,比较常用的options是输出所有显示所有类和成员、输出类的附件信息(如:堆栈大小、局部变量数量和方法的参数)

javap -v -p Test.class

其中,

  • -v:输出类的附件信息(如:堆栈大小、局部变量数量和方法的参数)
  • -p:显示所有的类和成员

2.2.3.梳理options

在官方文档中,javap的options很多,笔者做了这样的归类:

  • 与类有关的options:

-l:打印行和局部变量表

-package:显示程序包/受保护的/公共类和成员 (默认)

-public:只显示公共类和成员

-protected:只显示受保护的和公共的类和成员

-p -private:显示所有的类和成员

-s:打印内部类型签名

-constants:显示static final常量

-c:打印类中每个方法的反汇编代码,例如,组成Java 字节码 的指令

-v,-verbose:打印堆栈大小、局部变量数量和方法的参数

  • 与JVM有关的options:

CLASSPATH <path> ,-cp <path> :指定 java p命令用于查找类的路径。覆盖默认的CLASSPATH环境变量 -bootclasspath <path> :指定加载引导类的路径。默认情况下,引导类是实现位于jre/lib/rt.jar和其它几个jar -extdir dirs:覆盖扩展类的位置。扩展的默认位置是java.ext.dirs的值 Joption:将指定的选项传递给JVM(JVM的options详见java命令文档) eg: javap -J-version javap -J-Djava.security.manager -J-Djava.security.policy=MyPolicy MyClassName -sysinfo:显示正在处理的类的系统信息(路径、大小、日期、MD5哈希值)

2.2.4.javap的形和神

前面解读了javap的命令行手册,这些只能算作javap的形,也比较好掌握,我们看看官方文档的javap输出的例子:

 Compiled from " HelloWorld Frame.java"
public class HelloWorldFrame extends javax.swing.JFrame {
  java.lang.String message;
 
  public HelloWorldFrame();
    Code:
       0: aload_0
       1: invokespecial #1        // Method javax/swing/JFrame."<init>":()V
       4: aload_0
       5: ldc           #2        // String Hello World!
       7: putfield      #3        // Field message:Ljava/lang/String;
      10: aload_0
      11: new           #4        // class HelloWorldFrame$1
      14: dup
      15: aload_0
      16: invokespecial #5        // Method HelloWorldFrame$1."<init>":(LHelloWorldFrame;)V
      19: invokevirtual #6        // Method setContentPane:(Ljava/awt/Container;)V
      22: aload_0
      23: bipush        100
      25: bipush        100
      27: invokevirtual #7        // Method setSize:(II)V
      30: return
 
  public static void main(java.lang.String[]);
    Code:
       0: new           #8        // class HelloWorldFrame
       3: dup
       4: invokespecial #9        // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: iconst_1
      10: invokevirtual #10       // Method setVisible:(Z)V
      13: return
}  

不了解JVM字节码的程序猿依然看不懂,这些就是javap的神。

这就好像 辟邪剑法 辟邪剑谱 的关系。

接下来,我们就来解读一下javap的输出结果——字节码。

3.javap输出结果解读

3.1.字节码的基础知识

为了便于不太了解JVM字节码的程序猿更快进入下一章节,我们简单回顾和小结一下字节码的知识:

  • STEP1.我们通过java命令,将.java文件转化为.class文件(也就是字节码)
  • STEP2.字节码文件本身是什么呢?我们可以用16进制编辑器打开它

字节码文件本身就是一串字节流。

  • STEP3.为了看的更加清楚一些,我们将有关联的字节用同一种颜色着色:

如果您做过网络协议的开发,会发现字节码(字节流)与协议栈(如:ModBus)的逻辑很类似,几个字节为一组表达一个信息。

  • STEP4.抽象一下上述着色字节流,我们可以将Class文件结构抽象如下

字节码基础属性

常量池:占据字节码文件最大的篇幅

类的基本信息:包含类名的索引、类访问标识、父类名的索引、实现了多少接口等信息

字段列表:包含有多少字段,每个字段名的索引、访问标识等

方法列表:包含多少方法,每个方法名的索引、访问标识、方法的实现等

附加属性

3.2.通过示例代码,解读javap的输出

我们以一段示例代码,来解读javap的输出:

  • 示例代码:

  • javap输出
 javap -v -p Demo2.class

Classfile /C:/Demo2.class                                                                                            
  Last modified 2021-1-25; size 613 bytes                                                                            
  MD5 checksum f7c661d99330a1eefb32b6429e5a48b4                                                                      
  Compiled from "Demo2.java"                                                                                         
public class com.firelord.zsample.lang.jvm.frontcompiler.Demo2
  minor version: 0                                                                                                   
  major version: 52                                                                                                  
  flags: ACC_PUBLIC, ACC_SUPER                                                                                       
Constant pool:                                                                                                       
   #1 = Methodref          #7.#21         // java/lang/Object."<init>":()V                                           
   #2 = Fieldref           #6.#22         // com/firelord/zsample/lang/jvm/frontcompiler/Demo2.field1:I              
   #3 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;                              
   #4 = String             #25            // hello world                                                             
   #5 = Methodref          #26.#27        // java/io/PrintStream.println:(Ljava/lang/String;)V                       
   #6 = Class              #28            // com/firelord/zsample/lang/jvm/frontcompiler/Demo2                       
   #7 = Class              #29            // java/lang/Object                                                        
   #8 = Utf8               field1                                                                                    
   #9 = Utf8               I                                                                                         
  #10 = Utf8               <init>                                                                                    
  #11 = Utf8               ()V                                                                                       
  #12 = Utf8               Code                                                                                      
  #13 = Utf8               LineNumberTable                                                                           
  #14 = Utf8               LocalVariableTable                                                                        
  #15 = Utf8               this                                                                                      
  #16 = Utf8               Lcom/firelord/zsample/lang/jvm/frontcompiler/Demo2;                                       
  #17 = Utf8               hello                                                                                     
  #18 = Utf8               i                                                                                         
  #19 = Utf8               SourceFile                                                                                
  #20 = Utf8               Demo2.java                                                                                
  #21 = NameAndType        #10:#11        // "<init>":()V                                                            
  #22 = NameAndType        #8:#9          // field1:I                                                                
  #23 = Class              #30            // java/lang/System                                                        
  #24 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;                                               
  #25 = Utf8               hello world                                                                               
  #26 = Class              #33            // java/io/PrintStream                                                     
  #27 = NameAndType        #34:#35        // println:(Ljava/lang/String;)V                                           
  #28 = Utf8               com/firelord/zsample/lang/jvm/frontcompiler/Demo2                                         
  #29 = Utf8               java/lang/Object                                                                          
  #30 = Utf8               java/lang/System                                                                          
  #31 = Utf8               out                                                                                       
  #32 = Utf8               Ljava/io/PrintStream;                                                                     
  #33 = Utf8               java/io/PrintStream                                                                       
  #34 = Utf8               println                                                                                   
  #35 = Utf8               (Ljava/lang/String;)V                                                                     
{                                                                                                                    
  private int field1;                                                                                                
    descriptor: I                                                                                                    
    flags: ACC_PRIVATE                                                                                               
                                                                                                                     
  public com.firelord.zsample.lang.jvm.frontcompiler.Demo2();                                                        
    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 field1:I                                                      
         9: return                                                                                                   
      LineNumberTable:                                                                                               
        line 3: 0                                                                                                    
        line 4: 4                                                                                                    
      LocalVariableTable:                                                                                            
        Start  Length  Slot  Name   Signature                                                                        
            0      10     0  this   Lcom/firelord/zsample/lang/jvm/frontcompiler/Demo2;                              

  public void hello();                                                                                               
    descriptor: ()V                                                                                                  
    flags: ACC_PUBLIC                                                                                                
    Code:                                                                                                            
      stack=2, locals=2, args_size=1                                                                                 
         0: iconst_1                                                                                                 
         1: istore_1                                                                                                 
         2: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;                    
         5: ldc           #4                  // String hello world                                                  
         7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V            
        10: return                                                                                                   
      LineNumberTable:                                                                                               
        line 7: 0                                                                                                    
        line 8: 2                                                                                                    
        line 9: 10                                                                                                   
      LocalVariableTable:                                                                                            
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcom/firelord/zsample/lang/jvm/frontcompiler/Demo2;                              
            2       9     1     i   I                                                                                
}
SourceFile: "Demo2.java"                                                                                               

3.2.1.Class文件基础信息

Class文件的基础信息包括:

  • class文件的路径
  • class文件的修改时间、class文件的大小
  • class文件的MD5值
  • java文件的名称
  • jdk的大版本/小版本号

3.3.2.常量池

常量池的细节知识有很多,但是有3个关键点:

  • 访问权限、字符串等等,都是JVM所认为的”常量”
  • 常量池中存储了多种类型的常量
  • 常量之间以类似”指针”的形式来表达源代码

例如:class Demo2,那么常量池有就会有一个UTF-8类型的常量表示”Demo2″,还会有一个类的符号引用指向”Demo2″这个UTF-8的常量。

3.3.3.访问标识、类索引

在常量池的基础上,JVM首先要表达源文件中的类,类的关键要素包括:

  • 类名:javap的输出结果中有多处呈现了类名是Demo2。
  • 类的访问权限:本例中,javap的输出就是表示Demo2类是public的,并且继承于Object类

3.3.4.字段表

进一步,字节码要表达:

  • Demo2类中有几个字段:javap的输出告诉我们,Demo2只有1个字段field1
  • field1字段的数据类型:从输出可以看到,field1是int类型
  • field1字段的访问权限:从输出可以看出,field1是private

3.3.5.方法表

更进一步,字节码要表达:

  • Demo2类中有几个方法:本例中,有一个默认的构造函数,还有一个hello方法
  • 方法的原型:从输出看,hello方法的访问权限是public,返回值是void,没有输入参数
  • 方法的具体实现:从输出看,hello方法被转换为了73~88行的JVM指令序列、行号表、局部变量表。

4.总结

本文解读了javap的使用以及javap的输出结果,具体如下:

  • 理解字节码以及JVM价值
  • javap的学习方法
  • javap命令行常用option
  • javap命令行的options解读
  • 字节码的主体结构
  • 通过一个示例代码,演练了javap输出结果中各个section的含义

5.参考资料

#GUID-BE20562C-912A-4F91-85CF-24909F212D7F

《深入理解Java虚拟机:JVM高级特性与最佳实践》

文章来源:智云一二三科技

文章标题:「编译引擎」-学习阅读Class文件结构(javap版)

文章地址:https://www.zhihuclub.com/171130.shtml

关于作者: 智云科技

热门文章

网站地图