4.JVM类加载器深入解析及重要特性剖析

一.回顾完整的加载过程

1.完整过程

2.时序图

  • 类的加载最终产品是位于内存中的Class对象

  • Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区的数据结构的接口

二.类加载器

1.有两种类型的类加载器

  • Java虚拟机自带的加载器

    • 根类加载器(Bootstrap):该加载器没有父加载器,它负责加载虚拟机中的核心类库。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有集成java.lang.ClassLoader类。

    • 扩展类加载器(Extension):它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre\lib\ext子目录(扩展目录)下加载类库,如果把用户创建的jar文件放在这个目录下,也会自动由扩展类加载器加载,扩展类加载器是纯java类,是java.lang.ClassLoader的子类。

    • 系统应用类加载器(AppClassLoader/System):也称为应用类加载器,它的父加载器为扩展类加载器,它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,他是用户自定义的类加载器的默认父加载器。系统类加载器时纯java类,是java.lang.ClassLoader的子类。

  • 用户自定义的类加载器

    • java.lang.ClassLoader的子类

    • 用户可以定制类的加载方式

2.类的加载阶段

注:类加载器并不需要等到某个类被“首次主动使用”时再加载它,可以在之前预先加载。

  • JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类才报告错误(LinkageError错误)

  • 如果这个类没有被程序主动使用,那么类加载器也不会报告错误。

3.类的验证阶段

  • 类被加载后,就进入连接阶段。连接阶段就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。

  • 类的验证内容:

    • 类文件的结构检查

    • 语义检查

    • 字节码验证

    • 二进制兼容性的验证

4.类的准备阶段

  • 在准备阶段,java虚拟机为类的静态变量分配内存,并设置默认的初始值。

  • 例如对于以下Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0;

 1 public class Sample {
 2     private static int a = 1;
 3     public static long b;
 4     public static long c;
 5 
 6     static {
 7         b = 2;
 8     }
 9 }
10             

5.类的初始化阶段

  • 在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。

  • 在程序中,静态变量的初始化有两种途径:

    1. 在静态变量的声明处进行初始化;

    2. 在静态代码块中进行初始化。 

  • 类的初始化步骤

    1. 假如这个类还没有被加载和连接,那就先进行加载和连接

    2. 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类

    3. 假如类中存在初始化语句,那就依次执行这些初始化语句

注:当java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则不适用于接口。因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定的接口的静态变量时,才会导致该接口的初始化。

 

 1 public class MyTest5 {
 2 
 3     public static void main(String[] args) {
 4         System.out.println(classChild5.c);
 5     }
 6 
 7 }
 8 
 9 interface interfaceParent5 {
10 
11     Thread thread = new Thread() {
12 
13         {
14             System.out.println("interfaceParent5");
15         }
16     };
17 
18 }
19 
20 class classParent5 {
21 
22     static {
23         System.out.println("classParent5");
24     }
25 }
26 
27 class classChild5 extends classParent5 implements interfaceParent5 {
28 
29 
30     public static int c = 6;
31 
32     static {
33         System.out.println("classChild5");
34     }
35 
36 }
37 
38 /*
39  * 输出:
40  * classParent5
41  * classChild5
42  * 6
43  * */

 

总结:从上面代码执行结果可以看出,classChild5类初始化,classParent5类初始化,interfaceParent5接口没有初始化,子类初始化父类一定初始化但父接口不一定初始化。

 1 public class MyTest5 {
 2 
 3     public static void main(String[] args) {
 4         System.out.println(interfaceChild5.i);
 5     }
 6 
 7 }
 8 
 9 interface interfaceParent5 {
10 
11     Thread thread = new Thread() {
12 
13         {
14             System.out.println("interfaceParent5");
15         }
16     };
17 
18 }
19 
20 class classParent5 {
21 
22     static {
23         System.out.println("classParent5");
24     }
25 }
26 
27 
28 interface interfaceChild5 extends interfaceParent5{
29 
30     int[] i = new int[]{1,2,3,4,5};
31 
32     classParent5 cp = new classParent5();
33 
34 }
35 /*
36  * 输出:
37  * classParent5
38  * [I@1540e19d
39  * */

总结:子接口初始化但是父接口没有初始化,从上面看出使用new关键字的子接口或类一定会被初始化

 

6.获取类加载器方法

 


 

 

posted @ 2020-02-11 01:07  All_just_for_fun  阅读(242)  评论(0编辑  收藏  举报