【修炼内功】[JVM] 类文件结构

本文已收录 【修炼内功】跃迁之路

类文件结构

学习C语言的时候,须要在不一样的目标操做系统上(或者使用交叉编译环境),(使用正确的CPU指令集)编译成对应操做系统可运行的执行文件,才能够在相应的系统上运行,若是使用操做系统差别性的库或者接口,还须要针对不一样的系统作不一样的处理(宏)java

Java的出现也正是为了解决"平台无关性","Write Once, Run Anywhere"的口号也充分表达了软件开发人员对冲破平台接线的渴求segmentfault

"与平台无关"的最终实现仍是要在操做系统的应用层上,这即是JVM的存在,不一样的平台有不一样的JVM,而全部的JVM均可以载入与平台无关的字节码,从而实现程序的"一次编写,处处运行"数组

JVM并不是只为Java设计,而字节码也并不是只有Java才能够编译获得,早在Java发展之初,设计者便将Java规范拆分为Java语言规范Java虚拟机规范,同时也承诺,对JVM作适当的扩展,以便更好地支持其余语言运行于JVM之上,而这一切的基础即是Class文件(字节码文件),Class文件中存放了JVM能够理解运行的字节码命令并发

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

JVM并不关心Class的来源是何种语言,在JVM发展到1.7~1.8的时候,设计者经过JSR-292基本兑现了以上承诺app

class_compile

本篇不会去详细地介绍如何去解析Class文件,目的是为了了解Class文件的结构,Class文件中都包含哪些内容ide

Class文件能够由JVM加载并执行,其中记录了类信息、变量信息、方法信息、字节码指令等等,虽然JVM加载Class以后(在JIT以前)进行的是解释执行,但Class文件并非文本文件,而是被严格定义的二进制流文件布局

接下来,均会以这段代码为示例进行分析学习

import java.io.Serializable;

public class ClassStruct implements Serializable {
    private static final String HELLO = "hello";
    private String name;

    public ClassStruct(String name) {
        this.name = name;
    }

    public void print() {
        System.out.println(HELLO + ": " + name);
    }

    public static void main(String[] args) {
        ClassStruct classStruct = new ClassStruct("ManerFan");
        classStruct.print();
    }
}

使用$ javac ClassStruct.java进行编译,编译后的文件可使用$ javap -p -v ClassStruct查看Class文件的内容(见文章末尾)ui

魔数

不少文件存储都会使用魔数来进行身份识别,好比图片文件,即便将图片文件改成不正确的后缀,绝大多数图片预览器也会正确解析this

一样Class文件也不例外,使用二进制模式打开Class文件,会发现全部Class文件的前四个字节均为OxCAFEBABE,这个魔术在Java还被称为"Oak"语言的时候就已经肯定下来了

magic_number

版本号

紧接着魔术的四个字节(接下来再也不对照二进制进行查看,而是直接查看javap帮咱们解析出来的结果,见文章末尾)存储的是Class文件的版本号,前两个字节为次版本号(minor version),后两个字节为主版本号(major version)

class_1

Java版本号从45开始,高版本的JDK能够向下兼容低版本的Class文件,但没法向上兼容高版本,即便文件格式并未发生变化

常量池

紧接着主次版本号以后的是常量池入口

常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)

字面量:如文本字符串、被声明为final的常量值等
符号引用:类和接口的全限定名(Fully Qualified Name)、字段的名称和描述(Descriptor)、方法的名称和描述符

class_2

Java代码在进行编译时,并不像C或C++那样有"链接"这一步骤,而是在虚拟机加载Class文件时进行动态链接,Class文件中不会保存各方法和字段的内存布局,在虚拟机运行时,须要从常量池中得到对应的符号引用,再在类建立或运行时解析并翻译到具体的内存地址中,才能被虚拟机使用

访问标志

访问标志用于识别一些类或接口层次的访问信息

class_3

访问标志用于识别这个Class是类仍是接口;是否认义为public;是否为abstract类型;是否声明为final;等等,具体标志含义以下

标志 名称
ACC_PUBLIC 是否为public类型
ACC_FINAL 是否被声明为final
ACC_SUPER 是否容许使用invokespecial字节码指令
ACC_INTERFACE 是否为接口
ACC_ABSTRACT 是否为abstract
ACC_SYNTHETIC 标识这个类并不是由用户代码生成
ACC_ANNOTATION 标识这是一个注解
ACC_ENUM 标识这是一个枚举

类索引、父类索引、接口索引集合

Class文件中由类索引(this_class)、父类索引(super_class)及接口索引集合(interfaces)三项数据肯定这个类的继承关系

父类索引只有一个(对应extends语句),而接口索引则是一个集合(对应implements语句)

class_4

字段表集合

字段表(field_info)用于描述类或者接口中声明的变量

字段(field)包括了类级变量(如static)及实例级变量,但不包括在方法内部声明的变量

字段包含的信息有:做用域(public、private、protected)、类级仍是实例级(static)、可变性(final)、并发可见性(volatile)、能否序列化(transient)、数据类型、字段名等

class_5

描述符

这里简单解释一下描述符(descriptor)

描述符用来描述字段数据类型、方法参数列表和返回值,根据描述符规则,基本数据类型及表明无返回值的void类型都用一个大写字符表示,对象类型则用字符L加对象全限定名来表示

标识字符 含义
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V void
L 对象类型,如 Ljava/lang/Object;

对于数组,每个维度使用一个前置的[来描述,如java.lang.String[][]将被记录为[[java/lang/String;int[]将被记录为[I

描述方法时,按照先参数列表,后返回值的顺序描述,参数列表放在()内,如void inc()描述符为()V,方法java.lang.String toString()描述符为()Ljava/lang/String;,方法int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex的描述符为([CII[CIII)I

方法表集合

Class文件中对方法的描述与对字段的描述几乎采用了彻底一致的方式,方法表的结构依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes),可是方法内部的代码并不在方法表中,而是通过编译器编译成字节码指令后,存放在属性表集合中一个名为"Code"的属性中

class_6

属性表集合

在Class文件、字段表、方法表中均可以携带本身的属性表集合,用于描述某些场景专有的信息,Java虚拟机规范中预约义了9种虚拟机实现应当能识别的属性

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键自定义的常量值
Deprecated 类、方法表、字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
InnerClasses 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
SourceFile 类文件 原文件名称
Synthetic 类、方法表、字段表 标识方法或字段为编译器自动生成的

关于属性表,会在以后的文章中穿插介绍

附:Class文件

Classfile ~/articles/【修炼内功】跃迁之路/JVM/[JVM] 类文件结构/src/ClassStruct.class
  Last modified 2019-6-2; size 829 bytes
  MD5 checksum 9f7454acd0455837a33ff8e03edffdb3
  Compiled from "ClassStruct.java"
public class ClassStruct implements java.io.Serializable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #14.#31        // java/lang/Object."<init>":()V
   #2 = Fieldref           #6.#32         // ClassStruct.name:Ljava/lang/String;
   #3 = Fieldref           #33.#34        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Class              #35            // java/lang/StringBuilder
   #5 = Methodref          #4.#31         // java/lang/StringBuilder."<init>":()V
   #6 = Class              #36            // ClassStruct
   #7 = String             #37            // hello:
   #8 = Methodref          #4.#38         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #9 = Methodref          #4.#39         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #10 = Methodref          #40.#41        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = String             #42            // ManerFan
  #12 = Methodref          #6.#43         // ClassStruct."<init>":(Ljava/lang/String;)V
  #13 = Methodref          #6.#44         // ClassStruct.print:()V
  #14 = Class              #45            // java/lang/Object
  #15 = Class              #46            // java/io/Serializable
  #16 = Utf8               HELLO
  #17 = Utf8               Ljava/lang/String;
  #18 = Utf8               ConstantValue
  #19 = String             #47            // hello
  #20 = Utf8               name
  #21 = Utf8               <init>
  #22 = Utf8               (Ljava/lang/String;)V
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               print
  #26 = Utf8               ()V
  #27 = Utf8               main
  #28 = Utf8               ([Ljava/lang/String;)V
  #29 = Utf8               SourceFile
  #30 = Utf8               ClassStruct.java
  #31 = NameAndType        #21:#26        // "<init>":()V
  #32 = NameAndType        #20:#17        // name:Ljava/lang/String;
  #33 = Class              #48            // java/lang/System
  #34 = NameAndType        #49:#50        // out:Ljava/io/PrintStream;
  #35 = Utf8               java/lang/StringBuilder
  #36 = Utf8               ClassStruct
  #37 = Utf8               hello:
  #38 = NameAndType        #51:#52        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #39 = NameAndType        #53:#54        // toString:()Ljava/lang/String;
  #40 = Class              #55            // java/io/PrintStream
  #41 = NameAndType        #56:#22        // println:(Ljava/lang/String;)V
  #42 = Utf8               ManerFan
  #43 = NameAndType        #21:#22        // "<init>":(Ljava/lang/String;)V
  #44 = NameAndType        #25:#26        // print:()V
  #45 = Utf8               java/lang/Object
  #46 = Utf8               java/io/Serializable
  #47 = Utf8               hello
  #48 = Utf8               java/lang/System
  #49 = Utf8               out
  #50 = Utf8               Ljava/io/PrintStream;
  #51 = Utf8               append
  #52 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #53 = Utf8               toString
  #54 = Utf8               ()Ljava/lang/String;
  #55 = Utf8               java/io/PrintStream
  #56 = Utf8               println
{
  private static final java.lang.String HELLO;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
    ConstantValue: String hello

  private java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE

  public ClassStruct(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: aload_1
         6: putfield      #2                  // Field name:Ljava/lang/String;
         9: return
      LineNumberTable:
        line 7: 0
        line 8: 4
        line 9: 9

  public void print();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #4                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        10: ldc           #7                  // String hello:
        12: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: getfield      #2                  // Field name:Ljava/lang/String;
        19: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        22: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        25: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        28: return
      LineNumberTable:
        line 12: 0
        line 13: 28

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #6                  // class ClassStruct
         3: dup
         4: ldc           #11                 // String ManerFan
         6: invokespecial #12                 // Method "<init>":(Ljava/lang/String;)V
         9: astore_1
        10: aload_1
        11: invokevirtual #13                 // Method print:()V
        14: return
      LineNumberTable:
        line 16: 0
        line 17: 10
        line 18: 14
}
SourceFile: "ClassStruct.java"

参考:
深刻理解Java虚拟机

订阅号