字节码文章分为上下两篇,上篇也就是本文主要讲述class文件存在的意义,以及其带来的益处。并分析其内在构成之一 ———字节码,而下篇则从指令集方面着手,讲解指令集都有哪些,以及其各自表明的含义。最后总结一下Class文件存在的必然性。java
前面说过 Java 虚拟机拥有平台无关性,但其实如今语言无关性在 JVM 和更加的体现了出来。表现就是目前愈来愈多的语言能够在 JVM 上运行,而这背后的逻辑,就是这些语言都会被编译为 Class 文件,而后在JVM上 运行。jvm
In the future,we will consider bounded extensions to the java virtual machine to provide better support for other languages.ide
上面这段是 JVM 的相关人员提出的愿景,所以咱们也对 Java 语言的发展更加的看好。工具
那么在下面的文章中,咱们就来探讨 Class 文件的组成部分,了解其内部是如何组织的。3d
首先咱们编写一个原始的Java源代码:code
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提供的工具对代码进行编译,获得下面这个二进制流。blog
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"
咱们从头开始看起,首先是ip
minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER
这里分别为Class文件的次版本和主版本号以及访问标示,版本号前面已经说过了,这里就很少赘述了,至于访问标志,其表明了一些类或接口的访问信息,如是不是public类型的,有没有被声明成final等等。
接着到了 ConstantPool常量池,其能够简单理解为Class文件中的资源仓库,许多部分都与其关联。
其中存放了字面量和符号引用两种类常量。字面量能够理解为文本字符串和声明为final的常量值等。符号引号则包含如下内容:
上面两种类型在常量池中都是以表的形式来存储,其具体含义以下图所示:
针对常量池内每一个表的含义和咱们获得的class文件,在这做者分析几个看一下:
TestForEach
类中定义了int类型的属性dTestForEach
类,继承自Thread
ccc,d,dfinal
,全限定名java/lang/Thread
等,方法描述main,()V
等都属于这一类更多的这里就不详细展开了,但常量池中还有一些上面没有提到的内容,在这里咱们细说一下.
属性表集合用于描述某些场景中专有的信息.针对上文出现的属性表,咱们这里详细说下.
本文讲了 Class 文件在 Java 达成平台无关性和语言无关性起到的重要做用,并叙述了Class文件的重要组成部分----文件标识,常量池,属性集合等。此外还对文件标识和常量池的内容进行了具体的展开描述。
下篇文章则从 JVM 支持的指令集内容开始介绍,并以本文的 Class 文件的指令集集合为例,具体描述一下指令集的内容。
文章在公众号"iceWang"第一手更新,有兴趣的朋友能够关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯。
本系列文章主要借鉴自《深刻分析JavaWeb技术内幕》和《深刻理解Java虚拟机-JVM高级特性与最佳实践》。