• 类文件结构与javap的使用


    此文已由作者赵计刚薪授权网易云社区发布。

    欢迎访问网易云社区,了解更多网易技术产品运营经验。


    1、javap的使用与类文件结构

    使用过程:

    java源代码:

     1 package compile;
     2 /**
     3  * class字节码
     4  */
     5 public class TestClass {
     6     private int m;
     7     
     8     public int inc() {
     9         return m + 1;
    10     }
    11 }

    在硬盘上找到java源文件所在目录(eg.E:JavaworkspaceOfMyBatis3aseUtilsrccompile)

    打开命令窗口,执行"javac -g TestClass.java"生成TestClass.class字节码文件,然后使用"javap -c TestClass > TCC.txt"将字节码文件的处理结果输出到TCC.txt中。



    打开TCC.txt,如下:

    Compiled from "TestClass.java"
    public class compile.TestClass extends java.lang.Object{
        public compile.TestClass();
          Code:
           0:    aload_0
           1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
           4:    return
    
        public int inc();
          Code:
           0:    aload_0
           1:    getfield    #2; //Field m:I
           4:    iconst_1
           5:    iadd
           6:    ireturn
    }

    说明:

    • javac -g TestClass.java

      • -g:生成所有的调试信息,包括局部变量名和行号信息。

    • javap -c TestClass > TCC.txt,对于javap常用的参数:

      • -c:输出字节码Code

      • -l(小写L):输出Code、LineNumberTable与LocalVariableTable

      • -s:输出方法签名(方法的接收参数列表和返回值)

      • -verbose:包含-c、-l以及输出class文件的编译版本,常量池,Stack, Locals, Args_size

    • 对于javap而言,常用的就是-c或-verbose  

    这里列出使用"javap -verbose TestClass > TCV.txt的结果:

    Compiled from "TestClass.java"
    public class compile.TestClass extends java.lang.Object
      SourceFile: "TestClass.java" /* 源文件名称 */
      minor version: 0        /* 次版本号 */
      major version: 50        /* 主版本号,50-->jdk6 */
      Constant pool:        /* 常量池:存放所有的方法名、field名、方法签名(方法参数+返回值)、类型名、class文件中的常量值 */
        const #1 = Method    #4.#18;    //  java/lang/Object."<init>":()V
        const #2 = Field    #3.#19;    //  compile/TestClass.m:I
        const #3 = class    #20;    //  compile/TestClass    
        const #4 = class    #21;    //  java/lang/Object    
        const #5 = Asciz    m;                                /*field名*/
        const #6 = Asciz    I;                                /*类型名*/
        const #7 = Asciz    <init>;                            /*方法名(构造器)*/
        const #8 = Asciz    ()V;                             /*方法签名(方法参数+返回值)*/
        const #9 = Asciz    Code;
        const #10 = Asciz    LineNumberTable;                /*class文件中的常量值:Java源码的行号与字节码指令对应关系*/
        const #11 = Asciz    LocalVariableTable;                /*class文件中的常量值:局部变量表*/
        const #12 = Asciz    this;
        const #13 = Asciz    Lcompile/TestClass;;            /*当前类的类型"Lxxx;"表示xxx引用类型*/
        const #14 = Asciz    inc;                            /*方法名*/
        const #15 = Asciz    ()I;                            /*方法签名(方法参数+返回值)*/
        const #16 = Asciz    SourceFile;                        /*class文件中的常量值:源文件名称*/
        const #17 = Asciz    TestClass.java;                    /*class文件中的常量值:源文件名称*/
        const #18 = NameAndType    #7:#8;//  "<init>":()V
        const #19 = NameAndType    #5:#6;//  m:I
        const #20 = Asciz    compile/TestClass;                /*类型名*/
        const #21 = Asciz    java/lang/Object;                /*类型名*/
    
    {
        public compile.TestClass();
          Code:    /* 方法字节码 */
           /* Stack:操作数栈的深度(这个值就是类加载阶段为操作数栈分配的深度)
            * Locals:局部变量的分配空间(单位是slot,不是个数),对于double和long这两个64bit的,需要两个slot,对于其他<=32bit的,只需要一个slot
            * Args_size:方法参数的个数,包括方法参数、this(this只针对实例方法,static方法不会自动添加this)
            */
           Stack=1, Locals=1, Args_size=1 
           0:    aload_0    /*将第0个Slot中的引用类型的本地变量推到操作数栈顶,这里就是LocalVariableTable的this*/
           1:    invokespecial    #1; //Method java/lang/Object."<init>":()V  /* invokespecial #1:调用#1常量代表的方法,这里就是super(),当前栈顶的元素作为该方法#1的接收者 */
           4:    return    /*返回该方法,该方法的返回值为Void,执行了return指令,方法结束*/
           
          LineNumberTable: /* Java源码的行号与字节码指令对应关系 */
           line 5: 0
    
          LocalVariableTable: /* 局部变量表 */
           Start  Length  Slot  Name   Signature
           0      5      0    this       Lcompile/TestClass;
    
    
        public int inc();
          Code:
           Stack=2, Locals=1, Args_size=1
           0:    aload_0                            /*将第0个Slot中的引用类型的本地变量推到操作数栈顶,这里就是LocalVariableTable的this*/
           1:    getfield    #2; //Field m:I        /*getfield #2:获取常量表中定义的#2实例(即实例m),然后将m推到操作数栈顶*/
           4:    iconst_1                        /*向栈顶压入一个int常量1*/
           5:    iadd                            /*将栈顶的两个元素相加(这里是1和m),然后将结果压入栈顶*/
           6:    ireturn                            /*从当前方法返回栈顶的int型数值结果*/
          LineNumberTable: 
           line 9: 0
    
          LocalVariableTable: 
           Start  Length  Slot  Name   Signature
           0      7      0    this       Lcompile/TestClass;
    }

    说明:

    • 上述文件中/*xxx*/这样的注释是我添加的,//这样的注释是javap自己生成的

    • 需要知道的是,上述的文件并非是生成的*.class文件,*.class文件的内容是一串接近于机器码的十六进制字符,开头是一个魔数"0xCAFEBABE",该魔数是确定一个文件是否是class文件的标准。之后就是class编译版本(minor version,major version),然后下边的顺序与TCV.txt的顺序一样了。

    • 在TCV.txt文件中,多了一个无参构造器方法,该无参构造器调用的是TestClass的父类Object的无参构造器(即执行了super()方法),这个无参构造器是在javac变异的第三步"语义分析"的时候添加的,具体的查看第二章 Javac编译原理 

    注意:

    • 常量池的存放内容

      • 存放所有的方法名

      • field名

      • 方法签名(方法参数+返回值)

      • 类型名

      • class文件中的常量值

    • 常量池的前四部分可以称作是符号引用(即只有一些名称,但没有实际的地址,在运行期进行类的加载过后,会为这些东西分配实际的内存,到时候符号引用就会转化为直接引用,就能被JVM用了)

    • 常量池的组成:符号引用、常量(这个常量包含我们代码中定义的常量,eg、字符串常量,也包括class文件中的常量,eg.SourceFile)。

    • 主版本号的对应(eg.50对应jdk6,51对应jdk7),查看《深入理解java虚拟机(第二版)》P167

    • Stack:操作数栈的深度(这个值就是类加载阶段为操作数栈分配的深度)

    • Locals:局部变量的分配空间(单位是slot,不是个数),对于double和long这两个64bit的,需要两个slot,对于其他<=32bit的,只需要一个slot

    • Args_size:方法参数的个数,包括方法参数、this(this只针对实例方法,static方法不会自动添加this)

    • inc()方法:我详细注释了该方法的执行过程,这也就是JVM执行一个方法的基本流程(基于栈)

    提醒:

    • Code部分是我们主要关注的部分,这一部分中关键的部分就是每一条字节码指令的意义是什么。具体的可以查看《深入分析Java Web技术内幕(修订版)》P124-P135

    总结:

    • 掌握类文件结构,有利于我们理解类加载机制,而了解了类加载机制,最直接的好处,就是我们可以自己编写类加载工具,例如,smarty框架就是自己编写了一个类加载器

    • 读懂执行javap之后的字节码指令有利于我们理解java代码的执行流程,对我们定位问题也有一定的好处(虽然我在开发中还没有用这种方式定位过问题)


    免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

    更多网易技术、产品、运营经验分享请点击


    相关文章:
    【推荐】 DDoS 攻击与防御:从原理到实践(下)

  • 相关阅读:
    .net中Timer的使用
    计算日期的神器
    求全排列函数next_permutation
    各种排序
    求最大字段和
    炸弹时间复位
    最少步数,广搜
    数据
    水池数目
    最大岛屿
  • 原文地址:https://www.cnblogs.com/zyfd/p/10078139.html
Copyright © 2020-2023  润新知