class文件结构

244 阅读4分钟

为什么java具有跨平台能力

java能够实现“一次编译,到处运行”的原因

java虚拟机支持很多种语言,这些语言经过编译后可以生成能够被jvm解析并运行的字节码文件。

class文件数据结构

class文件只有两种数据结构:无符号数

无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者字符串(UTF-8 编码)。

表:表是由多个无符号数或者其他表作为数据项构成的复合数据类型,class文件中所有的表都以“_info”结尾。其实,整个 Class 文件本质上就是一张表。

class文件结构

JVM解析某个class文件,就是根据上图结构去解析class文件,加载class文件到内存,并在内存中分配相应空间

某种结构和占用空间如图:

魔数

在 class 文件开头的四个字节是 class 文件的魔数,它是一个固定的值--0XCAFEBABE。

如果开头四个字节不是 0XCAFEBABE, 那么就说明它不是 class 文件, 不能被 JVM 识别或加载。

版本号

紧跟在魔数后面的两个字节代表当前class文件的版本号。前两个字节0000代表次版本号(minor_version),后两个字节0034是主版本号(major_version),对应的十进进制值为 52,也就是说当前 class 文件的主版本号为 52,次版本号为 0。所以综合版本号是 52.0,也就是jdk1.8.0

常量池

在常量池中保存了类的各种相关信息,比如类的名称、父类的名称、类中的方法名、参数名称、参数类型等,

常量池中的每一项都是一个表,其项目类型共有 14 种,如下表所示:

例如:

CONSTANT_Methodref_info{
    u1 tag=10; 说明是CONSTANT_Methodref_info表
    u2 class_index;指向此方法的所属类
    u2 name_type_index;指向此方法的名称和类型
CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;指向某字段或方法的名称字符串
    u2 type_index;指向某字段或方法的类型字符串

0006:十进制 6,表示指向常量池中的第 6 个常量。

0015:十进制 21,表示指向常量池中的第 21 个常量。

最终值

访问标志

类索引、父类索引、接口索引计数器

字段表

字段表的主要功能是用来描述类或者接口中声明的变量

字段计数器:有几个字段

字段结构有:访问标志,指向NameAndType的索引

方法表

方法计数器:有几个方法

方法结构有:方法的访问标志,指向方法名,指向方法类型

属性表

JVM 中预定义了很多属性表,这里重点讲一下 Code 属性表。

Code 属性表中,最主要的就是一些列的字节码。

实例

Test类:

package com.example.study.reflect;

import java.io.Serializable;

public class Test implements Cloneable, Serializable {
	private int num = 1;

		public int add() {
		int i=2;
		int j=4;
		int result =i+j;
		return result+10;
	}

}

AndroidStudio自定义External Tools查看字节码

对应的字节码

"/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home\bin\javap" -c -verbose com.example.study.reflect.Test
Classfile /Users/yy/code/private/study_project/app/build/intermediates/javac/debug/classes/com/example/study/reflect/Test.class
  Last modified 2020-4-14; size 521 bytes
  MD5 checksum fea1a0449cdb4125e04f9536d0a93fdc
  Compiled from "Test.java"
public class com.example.study.reflect.Test implements java.lang.Cloneable,java.io.Serializable
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#23         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#24         // com/example/study/reflect/Test.num:I
   #3 = Class              #25            // com/example/study/reflect/Test
   #4 = Class              #26            // java/lang/Object
   #5 = Class              #27            // java/lang/Cloneable
   #6 = Class              #28            // java/io/Serializable
   #7 = Utf8               num
   #8 = Utf8               I
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/example/study/reflect/Test;
  #16 = Utf8               add
  #17 = Utf8               ()I
  #18 = Utf8               i
  #19 = Utf8               j
  #20 = Utf8               result
  #21 = Utf8               SourceFile
  #22 = Utf8               Test.java
  #23 = NameAndType        #9:#10         // "<init>":()V
  #24 = NameAndType        #7:#8          // num:I
  #25 = Utf8               com/example/study/reflect/Test
  #26 = Utf8               java/lang/Object
  #27 = Utf8               java/lang/Cloneable
  #28 = Utf8               java/io/Serializable
{
  public com.example.study.reflect.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_1
         6: putfield      #2                  // Field num:I
         9: return
      LineNumberTable:
        line 5: 0
        line 6: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lcom/example/study/reflect/Test;

  public int add();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_2
         1: istore_1
         2: iconst_4
         3: istore_2
         4: iload_1
         5: iload_2
         6: iadd
         7: istore_3
         8: iload_3
         9: bipush        10
        11: iadd
        12: ireturn
      LineNumberTable:
        line 9: 0
        line 10: 2
        line 11: 4
        line 12: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      13     0  this   Lcom/example/study/reflect/Test;
            2      11     1     i   I
            4       9     2     j   I
            8       5     3 result   I
}
SourceFile: "Test.java"

Process finished with exit code 0

java文件通过编译器编译成class文件,class文件通过类加载器加载到内存,jvm加载class文件到具体过程下一遍讲。

装载其中一个步骤就是JVM 会将这些 .class 文件的结构转化为 JVM 内部的运行时数据结构。这点同 JSON 解析过程有点类似。

学习于:

安卓工程师进阶34讲

Java二进制指令代码解析