Java基础02-类的加载过程02-解析

注:基于《Java高并发编程详解-汪文君》、《深入理解JVM高级特性与最佳实践-周志明》,以学习为目的,加上自己的理解、验证,作为自己的笔记,做到不复制,不粘贴。

回顾解析

类加载连接阶段包括验证,准备,解析。验证,准备阶段之后,就可以进入到解析阶段,所谓解析,就是在常量池中寻找类,接口,字段和方法的符号引用,并且将这些符号引用装换成直接引用的过程。

回顾符号引用

符号引用包括了下面三类常量

1.类和接口的全限定名

2.字段的名称和描述符

3.方法的名称和描述符

符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用的时候能够没有歧义的定位到目标即可。

符号引用于虚拟机实现的内存布局没有关系,引用的目标并不一定已经加载到内存中。

各种虚拟机实现的内存布局可以各不相同,但是他们能接受的符号引用必须是一致的,这是因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。

回顾直接引用

直接引用可以是直接指向目标的指针、相对偏移量或者是一个能够间接定位到目标的句柄。

直接引用是和虚拟机实现的内存布局相关的。同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般不会相同。但是如果有了直接引用,那么引用的目标必定已经在内存中存在。

javap命令

javap是jdk自带的一个命令,通过它可以查看一个java类反汇编、常量池、变量表、指令代码行表等等信息。

示例:

首先看这样的一段代码:

 1 package resolve;
 2 
 3 public class Resolve {
 4 
 5     static {
 6         System.out.println("I will be initialized.");
 7     }
 8 
 9     private static int i;
10     private double d;
11 
12     public static void print() {
13 
14     }
15 
16     private boolean trueOrFalse() {
17         return false;
18     }
19 
20 }
1 package resolve;
2 
3 public class ClassResolve {
4 
5     public static void main(String[] args) {
6         Resolve resolve = new Resolve();
7         System.out.println(resolve);
8     }
9 }

使用方式

接着我们就以上面的代码为例,使用javap命令,结果如下。

 1 D:\JavaDev\workspace\JavaProject\blogTest\target\classes\resolve>javap -verbose ClassResolve.class
 2 Classfile /D:/JavaDev/workspace/JavaProject/blogTest/target/classes/resolve/ClassResolve.class
 3   Last modified 2019-6-29; size 673 bytes
 4   MD5 checksum ca3094c7a548769953d80dc7970ec911
 5   Compiled from "ClassResolve.java"
 6 public class resolve.ClassResolve
 7   minor version: 0
 8   major version: 49
 9   flags: ACC_PUBLIC, ACC_SUPER
10 Constant pool:
11    #1 = Methodref          #8.#25         // java/lang/Object."<init>":()V
12    #2 = Fieldref           #26.#27        // java/lang/System.out:Ljava/io/PrintStream;
13    #3 = Fieldref           #7.#28         // resolve/ClassResolve.resolve:Lresolve/Resolve;
14    #4 = Methodref          #29.#30        // java/io/PrintStream.println:(Ljava/lang/Object;)V
15    #5 = Class              #31            // resolve/Resolve
16    #6 = Methodref          #5.#25         // resolve/Resolve."<init>":()V
17    #7 = Class              #32            // resolve/ClassResolve
18    #8 = Class              #33            // java/lang/Object
19    #9 = Utf8               resolve
20   #10 = Utf8               Lresolve/Resolve;
21   #11 = Utf8               <init>
22   #12 = Utf8               ()V
23   #13 = Utf8               Code
24   #14 = Utf8               LineNumberTable
25   #15 = Utf8               LocalVariableTable
26   #16 = Utf8               this
27   #17 = Utf8               Lresolve/ClassResolve;
28   #18 = Utf8               main
29   #19 = Utf8               ([Ljava/lang/String;)V
30   #20 = Utf8               args
31   #21 = Utf8               [Ljava/lang/String;
32   #22 = Utf8               <clinit>
33   #23 = Utf8               SourceFile
34   #24 = Utf8               ClassResolve.java
35   #25 = NameAndType        #11:#12        // "<init>":()V
36   #26 = Class              #34            // java/lang/System
37   #27 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
38   #28 = NameAndType        #9:#10         // resolve:Lresolve/Resolve;
39   #29 = Class              #37            // java/io/PrintStream
40   #30 = NameAndType        #38:#39        // println:(Ljava/lang/Object;)V
41   #31 = Utf8               resolve/Resolve
42   #32 = Utf8               resolve/ClassResolve
43   #33 = Utf8               java/lang/Object
44   #34 = Utf8               java/lang/System
45   #35 = Utf8               out
46   #36 = Utf8               Ljava/io/PrintStream;
47   #37 = Utf8               java/io/PrintStream
48   #38 = Utf8               println
49   #39 = Utf8               (Ljava/lang/Object;)V
50 {
51   static resolve.Resolve resolve;
52     descriptor: Lresolve/Resolve;
53     flags: ACC_STATIC
54 
55   public resolve.ClassResolve();
56     descriptor: ()V
57     flags: ACC_PUBLIC
58     Code:
59       stack=1, locals=1, args_size=1
60          0: aload_0
61          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
62          4: return
63       LineNumberTable:
64         line 3: 0
65       LocalVariableTable:
66         Start  Length  Slot  Name   Signature
67             0       5     0  this   Lresolve/ClassResolve;
68 
69   public static void main(java.lang.String[]);
70     descriptor: ([Ljava/lang/String;)V
71     flags: ACC_PUBLIC, ACC_STATIC
72     Code:
73       stack=2, locals=1, args_size=1
74          0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
75          3: getstatic     #3                  // Field resolve:Lresolve/Resolve;
76          6: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
77          9: return
78       LineNumberTable:
79         line 8: 0
80         line 9: 9
81       LocalVariableTable:
82         Start  Length  Slot  Name   Signature
83             0      10     0  args   [Ljava/lang/String;
84 
85   static {};
86     descriptor: ()V
87     flags: ACC_STATIC
88     Code:
89       stack=2, locals=0, args_size=0
90          0: new           #5                  // class resolve/Resolve
91          3: dup
92          4: invokespecial #6                  // Method resolve/Resolve."<init>":()V
93          7: putstatic     #3                  // Field resolve:Lresolve/Resolve;
94         10: return
95       LineNumberTable:
96         line 5: 0
97 }
98 SourceFile: "ClassResolve.java"

说明:

从第10行可以看到在常量池中所有常量,例如:#6表示第6个常量。另外,所有utf8都是表示符号引用。再看看下面这个含义:

#27 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;

 第27个常量的名称和类型,分别是第35个常量和第36常量所表示的内容。即:

45   #35 = Utf8               out
46   #36 = Utf8               Ljava/io/PrintStream;

从74行可以看到,在常量池中通过getstatic这个指令获取PrintStream,同样getStatic也适用于获取Resolve,然后通过invokevirtual指令将resolve传递给PrintStream的println方法,在字节码的执行过程中,getStatic被执行之前,就需要进行解析。

虚拟机规范定义了,anewarray,checkcast,getField,getStatic,instanceof,invokeinterface,invokespecial,invokestactic,invokevirtual,multianwarray,new,putfield,putstatic这13个操作符号引用的字节码指令之前,就必须对所有的符号提前进行解析。

注:在对类接口,字段,类方法和接口方法的解析中,分别对应于常量中的CONSTANT_Class_info、 CONSTANT_Field_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodred_info这四种类型的常量。

那么最后就来说一说每一种类型的解析。

类接口解析

1.假如上面的代码中的Resolve类不是一个数组类型,则在加载的过程中,需要先完成对Resolve类的加载,同样需要经历所有的类加载阶段。

2.如果Resolve是一个数组类型,则JVM就不需要完成对Resole的加载,只需要在JVM中生成一个能够代表该类型的数组对象,并且在堆内存中开辟一片连续的地址空间。

3.在类接口的解析完成之后,还需要进行符号引用的验证。

字段的解析

顾名思义,就是解析所访问类或者接口中的字段,在解析类或者变量的时候,如果该字段不存在,或者出现错误,就会抛出异常,不在进行下面的解析。

1.如果Resolve类本身就包含某一个字段,就直接返回这个字段的引用,当然,也要对该字段所属的类提前进行类加载。

2.若果Resolve类中不存在该字段,则会根据继承关系自下而上,查找父类或者接口的字段,如果找到,即可返回。同样,需要提前对找到的字段进行类的加载过程。

3.如果Resolve类中没有字段,一直找到了最上层的java.lang.Object还是没有找到,则表示查找失败,也就不再进行任何解析,会抛出NoSuchFieldError异常。

注:

这个规则解释了为什么子类重写了父类的字段之后能够生效的原因。因为找到子类的字段就直接初始化并且返回了。

未完。

 

 

 

 

 

 

 

posted @ 2019-06-29 16:45  门外大汉  阅读(195)  评论(0编辑  收藏  举报