双非本科准备秋招(12.1)—— JVM4:类文件结构与加载机制

        恢复元气,最后一天学JVM!学了五天JVM了,不打算学的太深,这几天收获也很多,对很多底层原理有了那么一点了解,以后肯定还会继续加深JVM的学习理解的,暂时先到此为止,接下来是为期一个星期的JUC并发编程学习。

类文件结构

虚拟机+字节码实现了java的平台无关性和语言无关性。

java程序不需要考虑运行在什么操作系统上,JVM也并非只能运行java代码,虚拟机只关心*.class字节码文件,能生成字节码文件的不只有java,还有Jruby、Groovy、Kotlin等。

字节码文件

一个Class文件唯一地对应着一个类或者接口的定义信息。

根据JVM规范,Class文件结构如下:

 ClassFile {
      u4                  magic;                                                                                     
      u2                  minor_ version;                                                                   
      u2                  major_ version;                                                                   
      u2                  constant_ pool_ count ;                                                    
      cp_info             constant_ pool[constant_ pool_ count-1];               
      u2                  access_ flags;                                                                        
      u2                  this_ class;
      u2                  super_ class;        
      u2                  interfaces_ count;
      u2                  interfaces[interfaces_ count];                     
      u2                  fields_ count ;
      field_info          fields[fields_ count];
      u2                  methods_ count; 
      method_ info        methods [methods_ count ] ;
      u2                  attributes_ count;                                                                                   
      attribute_ info     attributes[attributes_ count];
  }

Class文件有两种数据类型:

1、无符号数,u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节。

2、表,由多个无符号数和其他表构成的复合数据类型,命名一般以"_info"结尾,整个Class文件本质上也是一个表。

文件结构属性

比如以下java代码。

  public class test {
      public static void main(String[] args) {
          System.out.println("hello");
      }
  }

编译后的字节码文件对应如下。(注意这是16进制的)

  cafe babe 0000 0037 0022 0a00 0600 1409
  0015 0016 0800 170a 0018 0019 0700 1a07
  001b 0100 063c 696e 6974 3e01 0003 2829
  5601 0004 436f 6465 0100 0f4c 696e 654e
  756d 6265 7254 6162 6c65 0100 124c 6f63
  616c 5661 7269 6162 6c65 5461 626c 6501
  0004 7468 6973 0100 064c 7465 7374 3b01
  0004 6d61 696e 0100 1628 5b4c 6a61 7661
  2f6c 616e 672f 5374 7269 6e67 3b29 5601
  0004 6172 6773 0100 135b 4c6a 6176 612f
  6c61 6e67 2f53 7472 696e 673b 0100 0a53
  6f75 7263 6546 696c 6501 0009 7465 7374
  2e6a 6176 610c 0007 0008 0700 1c0c 001d
  001e 0100 0568 656c 6c6f 0700 1f0c 0020
  0021 0100 0474 6573 7401 0010 6a61 7661
  2f6c 616e 672f 4f62 6a65 6374 0100 106a
  6176 612f 6c61 6e67 2f53 7973 7465 6d01
  0003 6f75 7401 0015 4c6a 6176 612f 696f
  2f50 7269 6e74 5374 7265 616d 3b01 0013
  6a61 7661 2f69 6f2f 5072 696e 7453 7472
  6561 6d01 0007 7072 696e 746c 6e01 0015
  284c 6a61 7661 2f6c 616e 672f 5374 7269
  6e67 3b29 5600 2100 0500 0600 0000 0000
  0200 0100 0700 0800 0100 0900 0000 2f00
  0100 0100 0000 052a b700 01b1 0000 0002
  000a 0000 0006 0001 0000 0001 000b 0000
  000c 0001 0000 0005 000c 000d 0000 0009
  000e 000f 0001 0009 0000 0037 0002 0001
  0000 0009 b200 0212 03b6 0004 b100 0000
  0200 0a00 0000 0a00 0200 0000 0300 0800
  0400 0b00 0000 0c00 0100 0000 0900 1000
  1100 0000 0100 1200 0000 0200 13

1、魔数与文件版本

按照JVM规范,首先是u4 magic,一个字节8位,一个16进制的字符是4位,那么前四个字节就是ca fe ba be,这头四个字节就是魔数,代表这是个java的Class文件。

后面的0000 0037,前面的0000代表次版本号,后面0037代表高版本号。0x0037转成十进制为55,对应表格中的java SE 11

Java字节码文件版本号 JDK版本 产品版本号
· 1.0.x Java 1.0.x
45 1.1.x Java 1.1.x
46 1.2.x Java 1.2.x
47 1.3.x Java 1.3.x
48 1.4.x Java Java 2 Platform, Standard Edition (J2SE) 1.4.x
49 5.x Java 2 Platform, Standard Edition (J2SE) 5.0
50 6.x Java 2 Platform, Standard Edition (J2SE) 6.0
51 7.x Java SE 7
52 8.x Java SE 8
53 8.x Java SE 9
54 8.x Java SE 10
55 8.x Java SE 11
56 8.x Java SE 12
57 8.x Java SE 13
58 8.x Java SE 14
59 8.x Java SE 15
60 8.x Java SE 16
61 8.x Java SE 17
62 8.x Java SE 18

2、常量池

紧接着版本号后面就是常量池入口,这是第一个出现的表类型的数据项目,也是class文件中关联最多、占用空间最大的数据项之一。

下面是常量池类型与十进制的对应关系:

在0037的版本号之后,首先是0x0022(34),代表常量池有#1—#33项,#0不计入。

接下来0x0a(10),查表发现10对应是CONSTANT_Methodref,是一个方法引用,那接下来四个字节0x0006和0x0014代表引用了常量池中的对应项获得方法的类名和方法名。

字节码文件就是这样看的,大致了解这些即可,不要太钻细节。

那么,常量池中主要存放的是两种类型的常量:

1、字面量:文本字符串、final常量值等

2、符号引用:类和接口的全限定名、字段的名称和描述符、方法名称和描述符、方法句柄和方法类型等。

3、访问标志

常量池结束后的2个字节,代表访问标志,描述了这是类还是接口;是否为public;是否是abstract等。

下表可查到访问标志对应的十六进制。

4、索引

主要确认该类型的继承关系。类索引、父类索引都是一个u2的数据类型,接口索引是u2类型的集合。(单继承、多接口)

5、字段表属性

字段表(field_info),描述类或接口声明的变量。

6、方法表属性

包含方法的作用域、是否是static等等,与字段表类似。

7、属性表属性

为了方便读取信息,可以用javap反编译查看class文件。

java -verbose -p test.class

可以看到详细信息:

6:minor version: 0 //次版本号7:major version: 61 //主版本号

9: this_class: #8 // test 类索引

10: super_class: #2 // Omy 父类索引

12:Constant pool: 常量池

65—80:字段表属性

82—121:方法表属性

  Classfile /C:/Software/something/learning/1web/project/database-test/JVM/src/test.class
    Last modified 2024年1月31日; size 943 bytes                                      
    SHA-256 checksum b4966d64ed488e664920e7cee11605fde28faa7df79daf01d1c41a48d7022fbb
    Compiled from "test.java"                                                        
  public class test extends Omy                                                      
    minor version: 0       
    major version: 61          
    flags: (0x0021) ACC_PUBLIC, ACC_SUPER              
    this_class: #8                          // test    
    super_class: #2                         // Omy     
    interfaces: 0, fields: 4, methods: 2, attributes: 3
  Constant pool:
     #1 = Methodref          #2.#3          // Omy."<init>":()V
     #2 = Class              #4             // Omy
     #3 = NameAndType        #5:#6          // "<init>":()V
     #4 = Utf8               Omy
     #5 = Utf8               <init>
     #6 = Utf8               ()V
     #7 = Fieldref           #8.#9          // test.age:I
     #8 = Class              #10            // test
     #9 = NameAndType        #11:#12        // age:I
    #10 = Utf8               test
    #11 = Utf8               age
    #12 = Utf8               I
    #13 = Fieldref           #8.#14         // test.name:Ljava/lang/String;
    #14 = NameAndType        #15:#16        // name:Ljava/lang/String;
    #15 = Utf8               name
    #16 = Utf8               Ljava/lang/String;
    #17 = Fieldref           #8.#18         // test.card:Ljava/lang/String;
    #18 = NameAndType        #19:#16        // card:Ljava/lang/String;
    #19 = Utf8               card
    #20 = InvokeDynamic      #0:#21         // #0:makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    #21 = NameAndType        #22:#23        // makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    #22 = Utf8               makeConcatWithConstants
    #23 = Utf8               (ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    #24 = Utf8               sm
    #25 = Utf8               ConstantValue
    #26 = Integer            18
    #27 = Utf8               (ILjava/lang/String;Ljava/lang/String;)V
    #28 = Utf8               Code
    #29 = Utf8               LineNumberTable
    #30 = Utf8               getInfo
    #31 = Utf8               ()Ljava/lang/String;
    #32 = Utf8               SourceFile
    #33 = Utf8               test.java
    #34 = Utf8               BootstrapMethods
    #35 = MethodHandle       6:#36          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Lja
  va/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    #36 = Methodref          #37.#38        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/Me
  thodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    #37 = Class              #39            // java/lang/invoke/StringConcatFactory
    #38 = NameAndType        #22:#40        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lan
  g/Object;)Ljava/lang/invoke/CallSite;
    #39 = Utf8               java/lang/invoke/StringConcatFactory
    #40 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;     
    #41 = String             #42            // \u0001 \u0001 \u0001
    #42 = Utf8               \u0001 \u0001 \u0001
    #43 = Utf8               InnerClasses
    #44 = Class              #45            // java/lang/invoke/MethodHandles$Lookup
    #45 = Utf8               java/lang/invoke/MethodHandles$Lookup
    #46 = Class              #47            // java/lang/invoke/MethodHandles
    #47 = Utf8               java/lang/invoke/MethodHandles
    #48 = Utf8               Lookup
  {
    private static final int sm;
      descriptor: I
      flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
      ConstantValue: int 18
  ​
    private int age;
      descriptor: I
      flags: (0x0002) ACC_PRIVATE
  ​
    private java.lang.String name;
      descriptor: Ljava/lang/String;
      flags: (0x0002) ACC_PRIVATE
  ​
    public java.lang.String card;
      descriptor: Ljava/lang/String;
      flags: (0x0001) ACC_PUBLIC
  ​
    public test(int, java.lang.String, java.lang.String);
      descriptor: (ILjava/lang/String;Ljava/lang/String;)V
      flags: (0x0001) ACC_PUBLIC
      Code:
        stack=2, locals=4, args_size=4
           0: aload_0
           1: invokespecial #1                  // Method Omy."<init>":()V
           4: aload_0
           5: iload_1
           6: putfield      #7                  // Field age:I
           9: aload_0
          10: aload_2
          11: putfield      #13                 // Field name:Ljava/lang/String;
          14: aload_0
          15: aload_3
          16: putfield      #17                 // Field card:Ljava/lang/String;
          19: return
        LineNumberTable:
          line 8: 0
          line 9: 4
          line 10: 9
          line 11: 14
          line 12: 19
  ​
    public java.lang.String getInfo();
      descriptor: ()Ljava/lang/String;
      flags: (0x0001) ACC_PUBLIC
      Code:
        stack=3, locals=1, args_size=1
           0: aload_0
           1: getfield      #7                  // Field age:I
           4: aload_0
           5: getfield      #13                 // Field name:Ljava/lang/String;
           8: aload_0
           9: getfield      #17                 // Field card:Ljava/lang/String;
          12: invokedynamic #20,  0             // InvokeDynamic #0:makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
          17: areturn
        LineNumberTable:
          line 15: 0
  }
  SourceFile: "test.java"
  BootstrapMethods:
    0: #35 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;
      Method arguments:
        #41 \u0001 \u0001 \u0001
  InnerClasses:
    public static final #48= #44 of #46;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

语法糖(编译优化)

javac会对我们写的代码进行优化,减轻我们的工作量。举几个例子:

默认构造

编译器会给加上了默认的无参构造器。

  public class test{
      public static void main(String[] args) {
  ​
      }
  }
  public class test {
      public test() {
      }
  ​
      public static void main(String[] args) {
      }
  }

自动拆箱装箱

JDK5开始有的这个特性。

  public class test{
      public static void main(String[] args) {
          Integer a = 10;
          int b = a;
      }
  }

反编译一下:

可以看到调用了valueOf()和intValue()方法

泛型擦除

  import java.util.ArrayList;
  import java.util.List;
  ​
  public class test{
      public static void main(String[] args) {
          List<Integer> list = new ArrayList<>();
  ​
          list.add(10);
          list.add(12);
  ​
          Integer t = list.get(1);
  ​
      }
  }

调用add方法时,实际还是加入Object类型的数据

get也是

我们的泛型信息被保存在局部变量类型表里

类的生命周期

加载

1、通过类的全限定名获取类的二进制字节流

2、将字节流代表的静态存储结构转化为方法去运行时的数据结构。

3、在内存中生成一个代表这个类的Class对象,作为方法去该类的数据访问入口。

连接

验证

连接的第一步,检查是不是Class字节流,并且确保字节流符合要求、安全。

准备

为类的static变量分配内存,并初始化为默认值。

只会给的变量分配内存,不会给实例变量分配。

解析

将常量池的符号引用替换为直接引用,就是把虚拟的指向换成了内存中的真正地址了。

比如之前看到Constant Pool里的#2 #3之类的,这就是符号引用。

初始化

为类的static变量赋予正确的初始值(准备阶段是赋默认值)。

java有两种方式设置初始值。

public static int value = 123
public static int value;

static{

    value = 123;

}

类初始化的时机:只有主动使用类的时候,类才会初始化。

初始化的情况例如调用类的静态方法、访问类的静态变量、new关键字创建类的实例、Class.forName获取类。

不会初始化的情况,比如获取类的static变量。

类加载器

类加载器有一定层级关系,以JDK8为例

一个类加载器收到类加载请求,首先不会尝试去加载,而是将请求委派给父类加载器去完成,因此所有的加载请求都会传送到最顶层,只有父类加载器无法加载时,子加载器才会去完成加载,这就是双亲委派模型

相关推荐

  1. 本科准备11.2)—— 力扣字符串

    2024-02-01 07:08:02       38 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-01 07:08:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-01 07:08:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-01 07:08:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-01 07:08:02       18 阅读

热门阅读

  1. 使用Server-Sent Events实现后端主动向前端进行通信

    2024-02-01 07:08:02       37 阅读
  2. 机器学习-聚类算法Kmeans【手撕】

    2024-02-01 07:08:02       31 阅读
  3. 考研经验总结——政治篇

    2024-02-01 07:08:02       34 阅读
  4. 行为型设计模式—命令模式

    2024-02-01 07:08:02       32 阅读