Java——手撕字节码

这两天在看《深入理解Java虚拟机》,其中字节码那部分看得我极其烦躁,最后决定亲子拆解一遍它的例子,拆完后觉得印象深刻多了。

字节码拆解实验

对书上的例子进行了实现和分析,几个部分需要对照,相互印证,同时结合下面的理论部分才能明白。

Java代码

例子使用的Java代码和书上基本一样,只不过我写的时候包名不一样,代码如下:

package com.way.clazz;

public class TestClass {

    private int m;

    public int inc(){
        return m+1;
    }
}

拆解图示

不同的颜色的划线区域代表不同的字节码组成部分。下面的知识部分和图是一一对应的。

这里写图片描述

.class字节码

为了清洗,把原生的字节码放在这里
这里写图片描述

javap命令解析出来的结构:

Javap命令解析出的文件结构有助于对照验证。

Classfile /root/TestClass.class
  Last modified Mar 8, 2017; size 289 bytes
  MD5 checksum 796893667dc8e7d29bbece83875f670d
  Compiled from "TestClass.java"
public class com.way.clazz.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         // com/way/clazz/TestClass.m:I
   #3 = Class              #17            // com/way/clazz/TestClass
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               TestClass.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               com/way/clazz/TestClass
  #18 = Utf8               java/lang/Object
{
  public com.way.clazz.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 8: 0
}
SourceFile: "TestClass.java"

字节码理论

基本结构

Class文件的结构不像XML等描述语言那样松散自由。由于它没有任何分隔符号,所以,以上数据项无论是顺序还是数量都是被严格限定的。哪个字节代表什么含义,长度是多少,先后顺序如何,都不允许改变。

仔细观察上面的Class文件格式,可以看出Class文件格式采用一种类似于C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表。无符号数就是u1、u2、u4、u8来分别代表1个、2个、4个、8个字节。表是由多个无符号数或其他表构成的复合数据类型,以“_info”结尾。在表开始位置,通常会使用一个前置的容量计数器,因为表通常要描述数量不定的多个数据。

下图表示的就是Class文件格式的基本结构,所有_info就是表结构,它们的长度是不确定的。每个_info表的前两个字节会表示_info表的元素个数,根据元素个数,和每个元素的类型,可以确定最终长度。

这里写图片描述

Header头部

对应上面拆解图的灰色部分,长度固定,共8个字节:
1,MagicNum:4个字节,内容0xCAFEBABE,固定值
2,minor_version:2字节,JDK小版本号
3,major_version:2字节,JDK大版本号,决定着JVM能否处理该class文件,向下兼容。JDK1.8的主版本号是52

Constant Pool

常量池是一个表结构,表中元素可以是两种东西:字面量和符号引用

字面量:类似于Java语言层面所说的常量,如本文字符串,被声明为final的常量值。
符号引用:类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。

表中元素即一个常量的结构如下图:

这里写图片描述

以一个变量为例解释该表格的意义:

这里写图片描述

上图是常量池区域的开头部分,作为一个表结构,前两的字节必然是表元素数目,即0x08,0x09,存储的是0x0013,十进制是19,这里是从1开始计数,即存储了18个元素。接下来就是元素的具体内容。每一个元素的第一个字节都是类型的标识数字,如上表,标识数字是1~12,没有2,11种。第一个元素的头(0x10)值是0x0A,即10,查表,是CONSTANT_Methodref_info, 存储的是两个index,index即指针,索引,指向的已一定是常量池中的其他元素位置,这也就是所谓符号索引。CONSTANT_Methodref_info存储的是:指向声明方法接口描述符CONSTANT_class_info的索引;指向名称及类型描述符CONSTANT_NameAndType_info的索引。

看字节码,两个位置分别存储的是0x0004,0x000F,让我们顺藤摸瓜,看看到底是什么意思?0x0004指向常量表中第四号元素,0x000F指向第十五号元素。看解析内容,四号元素是CONSTANT_class_info类型,存储了指向十八号元素的索引,十八号元素是常量,存储的是“java/lang/Object”。再看十五号元素,十五号元素是CONSTANT_NameAndType_info,存储了一个名称索引和一个描述符索引,分别指向7号和8号,7号是,这是一个特殊标记,表示这个方法是构造器,8号是V,标识void,即空。

至此整个脉络搞清楚了,1号位存储的是一个方法的说明,这个方法返回值的Class存在了4号位,四号位指向了真是存储的常量字符串“java.lang.Object”,这个方法的名称和传入参数存在了15号位,15号位又分别指向了7号,和8号位,标识这个方法是构造器,传入参数为空。到这里我们就明白,这是类的共有构造函数的编译结果,即public com.way.clazz.TestClass(){},这个方法我们没有写,但是隐式存在的。

遵从以上方法,可以查询所有元素的含义,javap命令也就是做了这样的事情,并将结果以可理解的形式展示给我们。

常量池字节码的解析内容:

Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         // com/way/clazz/TestClass.m:I
   #3 = Class              #17            // com/way/clazz/TestClass
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               TestClass.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               com/way/clazz/TestClass
  #18 = Utf8               java/lang/Object

后面的访问标志,域,方法,类属性部分都可以根据以上方法一一拆解。将每个区域的数据结构列在下面

访问标签Access Right

这里写图片描述

字节码长度为u2,数值是上表中所有为真的标志取“或”运算。例如我们这里是ACC_PUBLIC,ACC_SUPER的,那么就是(0x0001|0x0020)得到0x0021。

索引 Inplement Interfaces

包含类索引,父索引和其它接口索引集合。类索引即对本类的索引,父索引即对继承父类的索引,如果没有继承类,就是java.lang.Object类,所有类都继承自它,它们都最终指向常量池中的值。接口索引是一个表,前两个字节是长度,后面的是对应个数的接口索引,本例没有其他接口。

字段区Field

这里写图片描述

字段区是表,前两字节是元素个数,之后的每一个元素遵从上表数据结构,attributes_info会连接在元素后面,其他是对常量池的索引。

其中Access_flag就是变量访问修饰符

这里写图片描述

Descriptor_index指的是数据的类型

这里写图片描述

方法区Method

方法区和字段区几乎一样

这里写图片描述

方法访问标识略有不同

这里写图片描述

属性表

上面的几个部分勾勒了一个类的框架,但一个字段和方法的具体内容则是在属性表里的。因此属性表是灵活和扩展性很强的。

虚拟机规定的属性类别如下:

这里写图片描述

本例中方法有code属性:

    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

对应的字节码是:

这里写图片描述

根据Code属性数据结构,可以一一对应解读。

这里写图片描述

Java字节码是JVM运行的的运行依据,JVM在字节码上采取了开放的策略,这也就是为什么有这么多的建立在JVM的语言,如Jython,Scala等。因为只要有相应的编译器将它们的语言文件编译成.class字节码,JVM都可以运行。而这些字节码的结构为什么如此与编译原理以及JVM类加载机制有关。而类加载机制是《深入理解JVM》的下一章节。

http://www.cnblogs.com/ITtangtang/p/3978102.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值