|
| 1 | +# Java 类加载机制 |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +## 阶段 |
| 6 | + |
| 7 | +### 加载 |
| 8 | + |
| 9 | +> 加载阶段是类加载过程的第一个阶段。在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中,接着会为这个类在 JVM 的方法区创建一个对应的 Class 对象,这个 Class 对象就是这个类各种数据的访问入口。 |
| 10 | +
|
| 11 | +把代码数据加载到内存中。 |
| 12 | + |
| 13 | +这里有两个重点: |
| 14 | + |
| 15 | +- **字节码来源**。一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译 |
| 16 | +- **类加载器**。一般包括**启动类加载器**,**扩展类加载器**,**应用类加载器**,以及用户的**自定义类加载器**。 |
| 17 | + |
| 18 | +### 验证 |
| 19 | + |
| 20 | +主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。 |
| 21 | + |
| 22 | +- 包括对于**文件格式的验证**,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息? |
| 23 | +- 对于**元数据的验证**,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载? |
| 24 | +- 对于**字节码的验证**,保证程序语义的合理性,比如要保证类型转换的合理性。 |
| 25 | +- 对于**符号引用的验证**,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问? |
| 26 | + |
| 27 | +### 准备(重点) |
| 28 | + |
| 29 | +**准备阶段主要是为类的静态变量分配内存,并设置jvm默认的初始值。对于非静态的变量,则不会为它们分配内存。** |
| 30 | + |
| 31 | +当完成字节码文件的校验之后,JVM 便会开始为类变量分配内存并初始化。这里需要注意两个关键点,即内存分配的对象以及初始化的类型。 |
| 32 | + |
| 33 | +- **内存分配的对象。**Java 中的变量有「类变量」和「类成员变量」两种类型,「类变量」指的是被 static 修饰的变量,而其他所有类型的变量都属于「类成员变量」。在准备阶段,JVM 只会为「类变量」分配内存,而不会为「类成员变量」分配内存。「类成员变量」的内存分配需要等到初始化阶段才开始。 |
| 34 | + |
| 35 | +例如下面的代码在准备阶段,只会为 factor 属性分配内存,而不会为 website 属性分配内存。 |
| 36 | + |
| 37 | +``` |
| 38 | +public static int factor = 3; |
| 39 | +public String website = "www.cnblogs.com/chanshuyi"; |
| 40 | +``` |
| 41 | + |
| 42 | +- **初始化的类型。**在准备阶段,JVM 会为类变量分配内存,并为其初始化。但是这里的初始化指的是为变量赋予 Java 语言中该数据类型的零值,而不是用户代码里初始化的值。 |
| 43 | + |
| 44 | +例如下面的代码在准备阶段之后,sector 的值将是 0,而不是 3。 |
| 45 | + |
| 46 | +``` |
| 47 | +public static int sector = 3; |
| 48 | +``` |
| 49 | + |
| 50 | +但如果一个变量是常量(被 static final 修饰)的话,那么在准备阶段,属性便会被赋予用户希望的值。例如下面的代码在准备阶段之后,number 的值将是 3,而不是 0。 |
| 51 | + |
| 52 | +``` |
| 53 | +public static final int number = 3; |
| 54 | +``` |
| 55 | + |
| 56 | +之所以 static final 会直接被复制,而 static 变量会被赋予零值。其实我们稍微思考一下就能想明白了。 |
| 57 | + |
| 58 | +两个语句的区别是一个有 final 关键字修饰,另外一个没有。而 final 关键字在 Java 中代表不可改变的意思,意思就是说 number 的值一旦赋值就不会在改变了。既然一旦赋值就不会再改变,那么就必须一开始就给其赋予用户想要的值,因此被 final 修饰的类变量在准备阶段就会被赋予想要的值。而没有被 final 修饰的类变量,其可能在初始化阶段或者运行阶段发生变化,所以就没有必要在准备阶段对它赋予用户想要的值。 |
| 59 | + |
| 60 | +### 解析 |
| 61 | + |
| 62 | +**解析过程就是查找类的常量池中的类,字段,方法,接口的符号引用,将他们替换成直接引用的过程。** |
| 63 | + |
| 64 | +两个重点: |
| 65 | + |
| 66 | +- **符号引用**。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。 |
| 67 | +- **直接引用**。可以理解为一个内存地址,或者一个偏移量。比如**类方法,类变量**的直接引用是指向方法区的**指针**;而**实例方法,实例变量**的直接引用则是从实例的头指针开始算起到这个实例变量位置的**偏移量** |
| 68 | + |
| 69 | +### 初始化(重点) |
| 70 | + |
| 71 | +这个阶段主要是对**类变量**初始化,是执行类构造器的过程。 |
| 72 | + |
| 73 | +- 换句话说,只对static修饰的变量或语句进行初始化。 |
| 74 | +- 如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。 |
| 75 | +- 如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。 |
| 76 | + |
| 77 | + |
| 78 | + |
| 79 | +到了初始化阶段,用户定义的 Java 程序代码才真正开始执行。在这个阶段,JVM 会根据语句执行顺序对类对象进行初始化,一般来说当 JVM 遇到下面 5 种情况的时候会触发初始化: |
| 80 | + |
| 81 | +- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。 |
| 82 | +- 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。 |
| 83 | +- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。 |
| 84 | +- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。 |
| 85 | +- 当使用 JDK1.7 动态语言支持时,如果一个 java.lang.invoke.MethodHandle实例最后的解析结果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。 |
| 86 | + |
| 87 | +### 使用 |
| 88 | + |
| 89 | +当 JVM 完成初始化阶段之后,JVM 便开始从入口方法开始执行用户的程序代码。 |
| 90 | + |
| 91 | +### 卸载 |
| 92 | + |
| 93 | +用户程序代码执行完毕后,JVM 便开始销毁创建的 Class 对象,最后负责运行的 JVM 也退出内存。 |
| 94 | + |
| 95 | +## 总结 |
| 96 | + |
| 97 | +从上面几个例子可以看出,分析一个类的执行顺序大概可以按照如下步骤: |
| 98 | + |
| 99 | +- **确定类变量的初始值。**在类加载的准备阶段,JVM 会为类变量初始化零值,这时候类变量会有一个初始的零值。如果是被 final 修饰的类变量,则直接会被初始成用户想要的值。 |
| 100 | +- **初始化入口方法。**当进入类加载的初始化阶段后,JVM 会寻找整个 main 方法入口,从而初始化 main 方法所在的整个类。当需要对一个类进行初始化时,会首先初始化类构造器(),之后初始化对象构造器()。 |
| 101 | +- **初始化类构造器。**JVM 会按顺序收集类变量的赋值语句、静态代码块,最终组成类构造器由 JVM 执行。 |
| 102 | +- **初始化对象构造器。**JVM 会按照收集成员变量的赋值语句、普通代码块,最后收集构造方法,将它们组成对象构造器,最终由 JVM 执行。 |
| 103 | + |
0 commit comments