编程

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

2019-06-01  本文已影响1人  冰尘缘

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文件,在这作者分析几个看一下:

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

属性表集合

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

总结

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

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

iceWang公众号

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

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

上一篇下一篇

猜你喜欢

热点阅读