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

图例说明:

这节继续偏理论化的来了解类加载的一些东东,先来看一张图:

该图描述了类加载顺序相关的一些信息,对图上的进行一下说明:假设有HelloApp这个类,首先会被类加载器所加载:

如果加载失败了则直接抛出异常:

而如果被类加载器给加载成功了接下来则处理链接阶段了:

当然此时就会涉及到如下阶段:

接着就到了类的初始化阶段:

最后就到了类的使用阶段,调用类的main方法:

最后就到了类的结束卸载阶段:

以上就完整的介绍的一个类完整的生命周期,接下来再来看一个更加详细的图:

下面对其也进行一下说明:

接着就是到类的验证阶段:

紧着着就到类的准备阶段,需要注意该阶段其值只是默认值,并非是我们设置的值,这个在上节中已经有体现:

接下来就到解析阶段了:

转而进入到类的初始化阶段了:

然后又到了类的实例化阶段了:

最后就到类的卸载阶段啦:

至此一个完整更加详细的过程就描述完了,这个图非常重要,将来都是以它进行论述的。

类的加载:

  • 类的加载的最终产品是位于内存中的Class对象。
  • Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
  • 有两种类型的类加载器:
  1. Java虚拟机自带的加载器
    ①、根类(启动类)加载器(Bootstrap)。
    ②、扩展类加载器(Extension)。
    ③、系统(应用)类加载器(System)。
  2. 用户自定义的类加载器
    ①、java.lang.ClassLoader的子类。
      这个类我想应该都知道,特别神秘,它是一个抽象类,也是所有自定义的直接继承类:
          
          在未来会详细学习到它。
    ②、用户可以定制类的加载方式。
  • 类的加载器并不需要等到某个类被“首次主动使用”时再加载它。
    这个用前面的一个例子再来说明一下,先来看一下程序:

    编译运行:

    很显然MyChild1并未主动首次使用,因为它的static块木有被执行,但是!!这里看一下类的加载信息【还是在run中加一个jvm参数-XX:+TraceClassLoading用来查看类的加载信息】:
     

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

类的验证:

  •  类被加载后,就进入连接阶段。连接就是将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去。
  • 类的验证的内容【并非所有内容,而是只列了主要的内容】:
    ①、类文件的结构检查。
    ②、语义检查。
    ③、字节码验证。
    ④、二进制兼容性的验证。

类的准备:

 在准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值。例如对于以下Sample类,在准备阶段,将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋预默认值0。程序如下:

public class Sample {
    private static int a = 1;
    private static long b;

    static {
        b = 2;
    }
    ...
}    

类的初始化:

在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。在程序中,静态变量的初始化有两种途径:(1)在静态变量的声明处进行初始化;(2)在静态代码块中进行初始化。例如在以下代码中,静态变量a 和 b都被显式初始化,而静态变量c没有被显式初始化,它将保持默认值0。

public class Sample {
    private static int a = 1; //在静态变量的声明处进行初始化
    private static long b;
    private static long c;

    static {
        b = 2;//在静态代码块中进行初始化
    }
    ...
}    

静态变量的声明语句,以及静态代码块都被看做类的初始化语句,Java虚拟机会按照初始化语句在类文件中的先后顺序来依次执行它们。例如当以下Sample类被初始化后,它的静态变量a的取值为4。

public class Sample{
  static int a = 1;
  static {
     a = 2;
  }      
  static {
     a = 4;
  }  
  public static void main(String args[]){
     System.out.println("a=" + a);   //打印a=4
  }
}

类的初始化步骤:

  • 假如这个类还没有被加载和连接,那就先进行加载和连接。
  • 假如类存在直接父类,并且这个父类还没有被初始化,那就先初始化直接父类。
  • 假如类存在初始化语句,那就依次执行这些初始化语句。

类的初始化时机:
这时又需要回顾非常重要类的七种主动使用方式了,如下:

接下来还有一个额外的说明:

  • 当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则不适用于接口【参考:http://www.cnblogs.com/webor2006/p/8904018.html】。
    ①、在初始化一个类时,并不会先初始化它所实现的接口。
    ②、在初始化一个接口时,并不会先初始化它的父接口。
    因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态变量时,才会导致接口的初始化。
  • 只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用。
  • 调用ClassLoader类的loadClass方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

类加载器:

类加载器用来把类加载到Java虚拟机中,从JDK1.2版本开始,类的加载过程采用父亲委托机制,这种机制能更好地保证Java平台的安全。在此委托机制中,除了Java虚拟机自带的根类加载器以外,其余的类加载器都有且只有一个父加载器。当Java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能加载,则由父加载器完成加载任务,否则才由加载器loader1本身加载Sample类。

而关于加载器的类型用下图进一步说明:

除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器。Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类。

最后看一张图,它表示了类加载器的一个层次关系,如下:

它们是一种双亲委托的关系,比如说系统类加载器尝试加载某一个类,它不会自己去加载,而会将其委托给“扩展类加载器”,而又会委托给“根类加载器”,如果它也加载不了,则会返回给“扩展类加载器”,如果它也加载不了则又会返回给“系统类加载器”,而它也加载不了则直接抛异常了,但凡以上加载器能加载则代表加载成功了,另外需要注意:貌似从关系图上看好像是系统类加载器继承至扩展类加载器,而扩展类加载器又继承根类加载器,其实不是这样的,而是一种包含关系:系统类加载器包含扩展类加载器,而扩展类加载器包含了根类加载器。关于这个在之后会进一步学习,先对理论有个大致的了解既可。

posted on 2018-04-22 10:29  cexo  阅读(479)  评论(0编辑  收藏  举报

导航