当前位置:   article > 正文

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

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

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

类文件结构

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

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

字节码文件

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

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

  1. ClassFile {
  2.   u4                 magic;                                                                                    
  3.   u2                 minor_ version;                                                                  
  4.   u2                 major_ version;                                                                  
  5.   u2                 constant_ pool_ count ;                                                    
  6.   cp_info             constant_ pool[constant_ pool_ count-1];              
  7.   u2                 access_ flags;                                                                        
  8.   u2                 this_ class;
  9.   u2                 super_ class;        
  10.   u2                 interfaces_ count;
  11.   u2                 interfaces[interfaces_ count];                    
  12.   u2                 fields_ count ;
  13.   field_info         fields[fields_ count];
  14.   u2                 methods_ count;
  15.   method_ info       methods [methods_ count ] ;
  16.   u2                 attributes_ count;                                                                                  
  17.   attribute_ info     attributes[attributes_ count];
  18. }

Class文件有两种数据类型:

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

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

文件结构属性

比如以下java代码。

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

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

  1. cafe babe 0000 0037 0022 0a00 0600 1409
  2. 0015 0016 0800 170a 0018 0019 0700 1a07
  3. 001b 0100 063c 696e 6974 3e01 0003 2829
  4. 5601 0004 436f 6465 0100 0f4c 696e 654e
  5. 756d 6265 7254 6162 6c65 0100 124c 6f63
  6. 616c 5661 7269 6162 6c65 5461 626c 6501
  7. 0004 7468 6973 0100 064c 7465 7374 3b01
  8. 0004 6d61 696e 0100 1628 5b4c 6a61 7661
  9. 2f6c 616e 672f 5374 7269 6e67 3b29 5601
  10. 0004 6172 6773 0100 135b 4c6a 6176 612f
  11. 6c61 6e67 2f53 7472 696e 673b 0100 0a53
  12. 6f75 7263 6546 696c 6501 0009 7465 7374
  13. 2e6a 6176 610c 0007 0008 0700 1c0c 001d
  14. 001e 0100 0568 656c 6c6f 0700 1f0c 0020
  15. 0021 0100 0474 6573 7401 0010 6a61 7661
  16. 2f6c 616e 672f 4f62 6a65 6374 0100 106a
  17. 6176 612f 6c61 6e67 2f53 7973 7465 6d01
  18. 0003 6f75 7401 0015 4c6a 6176 612f 696f
  19. 2f50 7269 6e74 5374 7265 616d 3b01 0013
  20. 6a61 7661 2f69 6f2f 5072 696e 7453 7472
  21. 6561 6d01 0007 7072 696e 746c 6e01 0015
  22. 284c 6a61 7661 2f6c 616e 672f 5374 7269
  23. 6e67 3b29 5600 2100 0500 0600 0000 0000
  24. 0200 0100 0700 0800 0100 0900 0000 2f00
  25. 0100 0100 0000 052a b700 01b1 0000 0002
  26. 000a 0000 0006 0001 0000 0001 000b 0000
  27. 000c 0001 0000 0005 000c 000d 0000 0009
  28. 000e 000f 0001 0009 0000 0037 0002 0001
  29. 0000 0009 b200 0212 03b6 0004 b100 0000
  30. 0200 0a00 0000 0a00 0200 0000 0300 0800
  31. 0400 0b00 0000 0c00 0100 0000 0900 1000
  32. 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.xJava 1.0.x
451.1.xJava 1.1.x
461.2.xJava 1.2.x
471.3.xJava 1.3.x
481.4.xJava Java 2 Platform, Standard Edition (J2SE) 1.4.x
495.xJava 2 Platform, Standard Edition (J2SE) 5.0
506.xJava 2 Platform, Standard Edition (J2SE) 6.0
517.xJava SE 7
528.xJava SE 8
538.xJava SE 9
548.xJava SE 10
558.xJava SE 11
568.xJava SE 12
578.xJava SE 13
588.xJava SE 14
598.xJava SE 15
608.xJava SE 16
618.xJava SE 17
628.xJava 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:方法表属性

  1. Classfile /C:/Software/something/learning/1web/project/database-test/JVM/src/test.class
  2.  Last modified 2024131日; size 943 bytes                                      
  3.  SHA-256 checksum b4966d64ed488e664920e7cee11605fde28faa7df79daf01d1c41a48d7022fbb
  4.  Compiled from "test.java"                                                        
  5. public class test extends Omy                                                      
  6.  minor version: 0      
  7.  major version: 61          
  8.  flags: (0x0021) ACC_PUBLIC, ACC_SUPER              
  9.  this_class: #8                          // test    
  10.  super_class: #2                         // Omy    
  11.  interfaces: 0, fields: 4, methods: 2, attributes: 3
  12. Constant pool:
  13.   #1 = Methodref          #2.#3          // Omy."<init>":()V
  14.   #2 = Class              #4             // Omy
  15.   #3 = NameAndType        #5:#6          // "<init>":()V
  16.   #4 = Utf8               Omy
  17.   #5 = Utf8               <init>
  18.   #6 = Utf8               ()V
  19.   #7 = Fieldref           #8.#9          // test.age:I
  20.   #8 = Class              #10            // test
  21.   #9 = NameAndType        #11:#12        // age:I
  22.  #10 = Utf8               test
  23.  #11 = Utf8               age
  24.  #12 = Utf8               I
  25.  #13 = Fieldref           #8.#14         // test.name:Ljava/lang/String;
  26.  #14 = NameAndType        #15:#16        // name:Ljava/lang/String;
  27.  #15 = Utf8               name
  28.  #16 = Utf8               Ljava/lang/String;
  29.  #17 = Fieldref           #8.#18         // test.card:Ljava/lang/String;
  30.  #18 = NameAndType        #19:#16        // card:Ljava/lang/String;
  31.  #19 = Utf8               card
  32.  #20 = InvokeDynamic      #0:#21         // #0:makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  33.  #21 = NameAndType        #22:#23        // makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  34.  #22 = Utf8               makeConcatWithConstants
  35.  #23 = Utf8               (ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  36.  #24 = Utf8               sm
  37.  #25 = Utf8               ConstantValue
  38.  #26 = Integer            18
  39.  #27 = Utf8               (ILjava/lang/String;Ljava/lang/String;)V
  40.  #28 = Utf8               Code
  41.  #29 = Utf8               LineNumberTable
  42.  #30 = Utf8               getInfo
  43.  #31 = Utf8               ()Ljava/lang/String;
  44.  #32 = Utf8               SourceFile
  45.  #33 = Utf8               test.java
  46.  #34 = Utf8               BootstrapMethods
  47.  #35 = MethodHandle       6:#36          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Lja
  48. va/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  49.  #36 = Methodref          #37.#38        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/Me
  50. thodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  51.  #37 = Class              #39            // java/lang/invoke/StringConcatFactory
  52.  #38 = NameAndType        #22:#40        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lan
  53. g/Object;)Ljava/lang/invoke/CallSite;
  54.  #39 = Utf8               java/lang/invoke/StringConcatFactory
  55.  #40 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;    
  56.  #41 = String             #42            // \u0001 \u0001 \u0001
  57.  #42 = Utf8               \u0001 \u0001 \u0001
  58.  #43 = Utf8               InnerClasses
  59.  #44 = Class              #45            // java/lang/invoke/MethodHandles$Lookup
  60.  #45 = Utf8               java/lang/invoke/MethodHandles$Lookup
  61.  #46 = Class              #47            // java/lang/invoke/MethodHandles
  62.  #47 = Utf8               java/lang/invoke/MethodHandles
  63.  #48 = Utf8               Lookup
  64. {
  65.  private static final int sm;
  66.    descriptor: I
  67.    flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
  68.    ConstantValue: int 18
  69.  private int age;
  70.    descriptor: I
  71.    flags: (0x0002) ACC_PRIVATE
  72.  private java.lang.String name;
  73.    descriptor: Ljava/lang/String;
  74.    flags: (0x0002) ACC_PRIVATE
  75.  public java.lang.String card;
  76.    descriptor: Ljava/lang/String;
  77.    flags: (0x0001) ACC_PUBLIC
  78.  public test(int, java.lang.String, java.lang.String);
  79.    descriptor: (ILjava/lang/String;Ljava/lang/String;)V
  80.    flags: (0x0001) ACC_PUBLIC
  81.    Code:
  82.      stack=2, locals=4, args_size=4
  83.         0: aload_0
  84.         1: invokespecial #1                  // Method Omy."<init>":()V
  85.         4: aload_0
  86.         5: iload_1
  87.         6: putfield      #7                  // Field age:I
  88.         9: aload_0
  89.        10: aload_2
  90.        11: putfield      #13                 // Field name:Ljava/lang/String;
  91.        14: aload_0
  92.        15: aload_3
  93.        16: putfield      #17                 // Field card:Ljava/lang/String;
  94.        19: return
  95.      LineNumberTable:
  96.        line 8: 0
  97.        line 9: 4
  98.        line 10: 9
  99.        line 11: 14
  100.        line 12: 19
  101.  public java.lang.String getInfo();
  102.    descriptor: ()Ljava/lang/String;
  103.    flags: (0x0001) ACC_PUBLIC
  104.    Code:
  105.      stack=3, locals=1, args_size=1
  106.         0: aload_0
  107.         1: getfield      #7                  // Field age:I
  108.         4: aload_0
  109.         5: getfield      #13                 // Field name:Ljava/lang/String;
  110.         8: aload_0
  111.         9: getfield      #17                 // Field card:Ljava/lang/String;
  112.        12: invokedynamic #20,  0             // InvokeDynamic #0:makeConcatWithConstants:(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;
  113.        17: areturn
  114.      LineNumberTable:
  115.        line 15: 0
  116. }
  117. SourceFile: "test.java"
  118. BootstrapMethods:
  119.  0: #35 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang
  120. /String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  121.    Method arguments:
  122.      #41 \u0001 \u0001 \u0001
  123. InnerClasses:
  124.  public static final #48= #44 of #46;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

语法糖(编译优化)

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

默认构造

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

  1. public class test{
  2.   public static void main(String[] args) {
  3.   }
  4. }
  1. public class test {
  2.   public test() {
  3.   }
  4.   public static void main(String[] args) {
  5.   }
  6. }

自动拆箱装箱

JDK5开始有的这个特性。

  1. public class test{
  2.   public static void main(String[] args) {
  3.       Integer a = 10;
  4.       int b = a;
  5.   }
  6. }

反编译一下:

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

泛型擦除

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. public class test{
  4.   public static void main(String[] args) {
  5.       List<Integer> list = new ArrayList<>();
  6.       list.add(10);
  7.       list.add(12);
  8.       Integer t = list.get(1);
  9.   }
  10. }

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

get也是

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

类的生命周期

加载

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

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

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

连接

验证

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

准备

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

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

解析

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

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

初始化

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

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

public static int value = 123
  1. public static int value;
  2. static{
  3. value = 123;
  4. }

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

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

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

类加载器

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/58870
推荐阅读
相关标签
  

闽ICP备14008679号