JVM:深入分析Java字节码-上

Posted jrookie

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM:深入分析Java字节码-上相关的知识,希望对你有一定的参考价值。

JVM(三):深入分析Java字节码-上

字节码文章分为上下两篇,上篇也就是本文主要讲述class文件存在的意义,以及其带来的益处。并分析其内在构成之一 ———字节码,而下篇则从指令集方面着手,讲解指令集都有哪些,以及其各自代表的含义。最后总结一下Class文件存在的必然性。

意义

前面说过 Java 虚拟机拥有平台无关性,但其实现在语言无关性在 JVM 和更加的体现了出来。表现就是目前越来越多的语言可以在 JVM 上运行,而这背后的逻辑,就是这些语言都会被编译为 Class 文件,然后在JVM上 运行。

In the future,we will consider bounded extensions to the java virtual machine to provide better support for other languages.

上面这段是 JVM 的相关人员提出的愿景,因此我们也对 Java 语言的发展更加的看好。

那么在下面的文章中,我们就来探讨 Class 文件的组成部分,了解其内部是如何组织的。

Class类文件

首先我们编写一个原始的Java源代码:

public class TestForEach extends Thread
private static int ccc = 1;
public static void main(String[] args) 

   int a = 1;
   int b = 2;
   int c = a + b;

这里我们使用JDk提供的工具对代码进行编译,得到下面这个二进制流。

    cafe babe 0000 0034 001d 0a00 0600 0f09
    0010 0011 0800 120a 0013 0014 0700 1507
    0016 0100 063c 696e 6974 3e01 0003 2829
    5601 0004 436f 6465 0100 0f4c 696e 654e
    756d 6265 7254 6162 6c65 0100 046d 6169
    6e01 0016 285b 4c6a 6176 612f 6c61 6e67
    2f53 7472 696e 673b 2956 0100 0a53 6f75
    .........

对我们来说,所需要分析的就是这个文件。该二进制流的前4个字节cafe babe,其被称为魔数。它代表了这是一个.class类型的文件,紧接着的第五第六个字节为次版本号,第七第八个字节为主版本号,而我们编译的这个版本是在 JDK1.8 下。再紧接着就是常量池,访问标示等信息,因为这些信息计算十分的繁琐麻烦,在这里就不展开来计算了,而官方也细心地提供了javap 工具进行反编译来查看其组织形式。

字节码文件

首先我们采用javap工具进行反编译javap -verbose TestForEach,得到如下文件

      Last modified 2019-5-22; size 461 bytes
      MD5 checksum 2602dfd883d5d5e417e26ce2d42b916d
      Compiled from "TestForEach.java"
    public class TestForEach extends java.lang.Thread
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#22         // java/lang/Thread."<init>":()V
       #2 = Fieldref           #5.#23         // TestForEach.d:I
       #3 = Fieldref           #5.#24         // TestForEach.dfinal:I
       #4 = Fieldref           #5.#25         // TestForEach.ccc:I
       #5 = Class              #26            // TestForEach
       #6 = Class              #27            // java/lang/Thread
       #7 = Utf8               ccc
       #8 = Utf8               I
       #9 = Utf8               d
      #10 = Utf8               dfinal
      #11 = Utf8               ConstantValue
      #12 = Integer            2222
      #13 = Utf8               <init>
      #14 = Utf8               ()V
      #15 = Utf8               Code
      #16 = Utf8               LineNumberTable
      #17 = Utf8               main
      #18 = Utf8               ([Ljava/lang/String;)V
      #19 = Utf8               <clinit>
      #20 = Utf8               SourceFile
      #21 = Utf8               TestForEach.java
      #22 = NameAndType        #13:#14        // "<init>":()V
      #23 = NameAndType        #9:#8          // d:I
      #24 = NameAndType        #10:#8         // dfinal:I
      #25 = NameAndType        #7:#8          // ccc:I
      #26 = Utf8               TestForEach
      #27 = Utf8               java/lang/Thread
    
      public TestForEach();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Thread."<init>":()V
             4: aload_0
             5: iconst_1
             6: putfield      #2                  // Field d:I
             9: aload_0
            10: sipush        2222
            13: putfield      #3                  // Field dfinal:I
            16: return
          LineNumberTable:
            line 11: 0
            line 17: 4
            line 18: 9
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=4, args_size=1
             0: iconst_2
             1: istore_2
             2: iconst_1
             3: iload_2
             4: iadd
             5: istore_3
             6: return
          LineNumberTable:
            line 22: 0
            line 23: 2
            line 111: 6
    
      static ;
        descriptor: ()V
        flags: ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: iconst_1
             1: putstatic     #4                  // Field ccc:I
             4: return
          LineNumberTable:
            line 16: 0
    
    SourceFile: "TestForEach.java"

文件头

我们从头开始看起,首先是

      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER

这里分别为Class文件的次版本和主版本号以及访问标示,版本号前面已经说过了,这里就不多赘述了,至于访问标志,其代表了一些类或接口的访问信息,如是否是public类型的,有没有被声明成final等等。

常量池

接着到了 ConstantPool常量池,其可以简单理解为Class文件中的资源仓库,许多部分都与其关联。

其中存放了字面量和符号引用两种类常量。字面量可以理解为文本字符串和声明为final的常量值等。符号引号则包含以下内容:

  • 类和接口的全限定名;
  • 字段的名称和描述符;
  • 方法的名称和描述符。

上面两种类型在常量池中都是以表的形式来存储,其具体含义如下图所示:
技术图片

技术图片

针对常量池内每个表的含义和我们得到的class文件,在这作者分析几个看一下:

  • Methodref:方法的符号引用,具体内容跳到6和22行,但从后面的注释可以看到是父类Thread的构造方法
  • Fieldref:字段的符号引用,具体内容在5,23行,可以看到是在TestForEach类中定义了int类型的属性d
  • Class:类或接口的符号引用,从中可以看出该文件是TestForEach类,继承自Thread
  • Utf8:表明其是一个字符串,在文件中字段描述ccc,d,dfinal,全限定名java/lang/Thread等,方法描述main,()V等都属于这一类
  • NameAndType:字段或方法的符号引用,与上面不同的是,其没有声明是哪一个类的
  • .....

更多的这里就不详细展开了,但常量池中还有一些上面没有提到的内容,在这里我们细说一下.

属性表集合

属性表集合用于描述某些场景中专有的信息.针对上文出现的属性表,我们这里详细说下.

  • ConstantValue:final关键字定义的常量值;
  • Code:Java代码编译而成字节码指令,具体指令含义在下篇中细说;
  • LineNumberTable:Java源码行号和字节码指令的对应关系;
  • SourceFile:源文件的名称
  • .........

总结

本文讲了 Class 文件在 Java 达成平台无关性和语言无关性起到的重要作用,并叙述了Class文件的重要组成部分----文件标识,常量池,属性集合等。此外还对文件标识和常量池的内容进行了具体的展开描述。

下篇文章则从 JVM 支持的指令集内容开始介绍,并以本文的 Class 文件的指令集集合为例,具体描述一下指令集的内容。

技术图片

文章在公众号"iceWang"第一手更新,有兴趣的朋友可以关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯。

本系列文章主要借鉴自《深入分析JavaWeb技术内幕》和《深入理解Java虚拟机-JVM高级特性与最佳实践》。

以上是关于JVM:深入分析Java字节码-上的主要内容,如果未能解决你的问题,请参考以下文章

JVM技术专题 深入分析class字节码指令方法调用详解「原理篇」

idea字节码插件JClassLib深入解读JVM字节码

深入jvm字节码

深入jvm字节码

深入理解JVM-java字节码文件结构剖析

深入理解JVM虚拟机5:虚拟机字节码执行引擎