首页 > 技术文章 > java类的加载机制

gekh 2018-11-15 16:08 原文

1. 什么是类的加载:

  类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

                                                      

 

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

  ·  

2. 加载.class文件的方式:
  – 从本地系统中直接加载
  – 通过网络下载.class文件
  – 从zip,jar等归档文件中加载.class文件
  – 从专有数据库中提取.class文件
  – 将Java源文件动态编译为.class文件

3. 类的生命周期:

        

 

    其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

  加载阶段: 查找并加载类的二进制数据。

    加载阶段,虚拟机需要完成以下三件事情:    

         1、通过一个类的全限定名来获取其定义的二进制字节流。

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

         3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。  

    加载阶段完成后,虚拟机外部的 二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个    java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。

4.类加载器:

  类加载器的层次关系如下图所示: 

        

 

   注意:这里父类加载器并不是通过继承关系来实现的,而是采用组合实现的。

  类加载器可以大致划分为以下三类:

    

  启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的

  扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

  应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

  应用程序都是由这三种类加载器互相配合进行加载的,如果有必要,我们还可以加入自定义的类加载器。因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,因此如果编写了自己的ClassLoader,便可以做到如下几点:

1)在执行非置信代码之前,自动验证数字签名。

2)动态地创建符合用户特定需要的定制化构建类。

3)从特定的场所取得java class,例如数据库中和网络中。

 

类加载有三种方式:

1、命令行启动应用时候由JVM初始化加载

2、通过Class.forName()方法动态加载

3、通过ClassLoader.loadClass()方法动态加载

  

public static void main(String[] args) throws ClassNotFoundException {
  // TODO Auto-generated method stub
  ClassLoader loader = LoaderTest.class.getClassLoader();
  System.out.println(loader);

  //使用ClassLoader.loadClass()来加载类,不会执行静态代码块
  loader.loadClass("test.classLoader.Test");

  //使用Class.forName()来加载类,默认会执行静态代码块
  Class.forName("test.classLoader.Test");

  //可以指定是否执行静态代码块
  Class.forName("test.classLoader.Test", true, loader);

}

java程序的初始化一般遵循以下三个原则,

  ①:静态对象(变量)优于非静态对象(变量),其中,静态变量只初始化一次,而非静态对象(变量)初始化多次。

  ②:父类优先于子类进行初始化。

  ③:按照成员变量定义顺序进行初始化,及时变量定义散布于块之中,他们依然在任何方法(包括构造方法)被调用之前初始化。

java类的加载顺序:

  父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量、父类非静态代码块、父类构造方法、

子类非静态变量、子类非静态代码块、子类构造方法。

5.双亲委派模型:

  双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

  

双亲委派机制:

 

1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

 

2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

 

3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

 

4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

 

双亲委派模型意义:

  -系统类防止内存中出现多份同样的字节码

  -保证Java程序安全稳定运行

 

 

 

 

 

 

  

    

 

 

推荐阅读