类文件结构

时间:2020-01-25 14:50:29   收藏:0   阅读:91

前言

我们知道我们写完的Java程序经过javac xxx.java编译后生成了xxx.class文件,那么现在我们就一起通过解析一个.class文件来深入的学习一下类文件结构。
备注:以下所有内容均整合于《深入理解jvm虚拟机》

Class类文件结构

package com.example.demo;

public class TestClass {

    private String str;

    public String getStr(){
        return str + "Test";
    }
}

下图是使用十六进制编辑器WinHex打开这个class文件的结果
技术分享图片

魔数与Class文件的版本

Class文件的头4个字节称为魔数(Magic Number),可以清楚的看到开头四个字节为0xCAFEBABE,它的唯一作用:确定这个文件是否为一个被虚拟机接受的Class文件
代表此版本号的第五个和第六个字节值为0x0000,而主版本号为0x0039,也就是十进制的57(对应jdk的版本为jdk13)
注:Java的版本号是从45开始的,JDK1.1之后的每一个JDK大版本发布主版本号向上加1,高版本的JDK能向下兼容低版本的JDK。

常量池

紧接着主次版本号后面的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,也是class文件中第一个出现的表类型数据项目。
由于常量池中常量的数据量是不固定的,所以在常量池的入口放置一项u2类型的数据,代表常量池容量技术值(constant_pool_count)。此容量计数从1开始
也就是说,常量池中常量的个数是这个容器计数-1。设计者将0空出来的目的是满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义。class文件中只有常量池的容量计数是从1开始的,对于其它集合类型,比如接口索引集合、字段表集合、方法表集合等的容量计数都是从0开始的。
常量池主要存放两大类常量:字面量(Literal)和符号引用。字面量比较接近于Java层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括下面三类常量:

上面分析了一个常量,为了避免繁琐,后续的19个常量计算借助于工具完成。在JDK的bin目录中,Java已经为我们提供了一个解析常量池的工具javap,我们可以通过javap -verbose class文件名,就可以自动帮我们解析了,下面是这个程序的解析结果:

C:\Users\user\Desktop>javap -verbose TestClass
警告: 文件 .\TestClass.class 不包含类 TestClass
Classfile /C:/Users/user/Desktop/TestClass.class
  Last modified 2020年1月25日; size 880 bytes
  SHA-256 checksum 69ca1079e58fc02cd1ac9294ceb6a2e2c67619fc9474e46e6771de8e27bff0a2
  Compiled from "TestClass.java"
public class com.example.demo.TestClass
  minor version: 0
  major version: 57
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #8                          // com/example/demo/TestClass
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 2, attributes: 3
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // com/example/demo/TestClass.str:Ljava/lang/String;
   #8 = Class              #10            // com/example/demo/TestClass
   #9 = NameAndType        #11:#12        // str:Ljava/lang/String;
  #10 = Utf8               com/example/demo/TestClass
  #11 = Utf8               str
  #12 = Utf8               Ljava/lang/String;
  #13 = InvokeDynamic      #0:#14         // #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
  #14 = NameAndType        #15:#16        // makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
  #15 = Utf8               makeConcatWithConstants
  #16 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lcom/example/demo/TestClass;
  #22 = Utf8               getStr
  #23 = Utf8               ()Ljava/lang/String;
  #24 = Utf8               SourceFile
  #25 = Utf8               TestClass.java
  #26 = Utf8               BootstrapMethods
  #27 = MethodHandle       6:#28          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #28 = Methodref          #29.#30        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #29 = Class              #31            // java/lang/invoke/StringConcatFactory
  #30 = NameAndType        #15:#32        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #31 = Utf8               java/lang/invoke/StringConcatFactory
  #32 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #33 = String             #34            // \u0001Test
  #34 = Utf8               \u0001Test
  #35 = Utf8               InnerClasses
  #36 = Class              #37            // java/lang/invoke/MethodHandles$Lookup
  #37 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #38 = Class              #39            // java/lang/invoke/MethodHandles
  #39 = Utf8               java/lang/invoke/MethodHandles
  #40 = Utf8               Lookup

从上述代码清单可以看出,已经计算出40个常量,且第1项计算结果与我们手工计算一致。最终的结果是后面显示的java/lang/Object."":()V,而且我们会发现并没有在Java程序中出现,还有一些内容也没有在Java程序中出现,比如“[”、“V”、“LineNumberTable”等。这是自动生成的常量,但它们会被后面即将介绍到的字段表、方法表和属性表引用到,用来描述一些不方便使用固定字节表示的内容。譬如描述方法的返回值是什么?有几个参数?每个参数的类型是什么?
最后,给出14种常量项的结构:
技术分享图片
技术分享图片

表3-3

访问标志

在常量池结束之后,紧接着的两个字节代表访问标志,这个标志用于标识一些类或接口层次的访问信息,包括:这个Class是接口还是类;是否定义为public;是否定义为abstract;如果是类的话,是否被声明为final等。具体的标志位以及含义如下表3-4:
技术分享图片

表3-4

技术分享图片
access_flags 中一共有16个标志位可以使用,当前只定义了8个,没有使用到的标志位要求一律为0。以TestClass为例
它的访问标志值是0x0021,查表可知,这是ACC_PUBLIC和ACC_SUPER值取或运算的结果。所以TestClass这个类的访问标志就是ACC_PUBLIC和ACC_SUPER,这一点我们可以在javap得到的结果中验证:
技术分享图片

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

原文:https://www.cnblogs.com/MacrossFT/p/12232926.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!