Java类加载机制

1. 类加载

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

2. 类的生命周期

  类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸载(Unloading)7个阶段,为了方便记忆,可以记:LVPRIUU。其中验证准备解析3个部分统称为连接(Linking)。如图所示。
  

  加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。

  • 加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象.
  • 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
  • 准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
  • 解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用
  • 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

3. 类加载器

  站在Java虚拟机的角度来讲,只存在两种不同的类加载器:

  • 启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分;
  • 所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。

  站在Java开发人员的角度来看,类加载器可以大致划分为以下三类:

  • 启动类加载器Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。
  • 扩展类加载器Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
  • 应用程序类加载器Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

4. 双亲委派模型(Parents Delegation Model)

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

双亲委派机制:

  1. AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
  2. ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
  3. 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
  4. ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException

ClassLoader源码分析:

 1     protected Class<?> loadClass(String name, boolean resolve)
 2         throws ClassNotFoundException
 3     {
 4         synchronized (getClassLoadingLock(name)) {
 5             // First, check if the class has already been loaded
 6             //首先,检查请求的类是否已经被加载过了
 7             Class<?> c = findLoadedClass(name);
 8             if (c == null) {
 9                 long t0 = System.nanoTime();
10                 try {
11                     if (parent != null) {
12                           //if parent is not null,delegate to parent to load class
13                         c = parent.loadClass(name, false);
14                     } else {
15                           //Returns a class loaded by the bootstrap class loader;or return null if not found.
16                         c = findBootstrapClassOrNull(name);
17                     }
18                 } catch (ClassNotFoundException e) {
19                     // ClassNotFoundException thrown if class not found
20                     // from the non-null parent class loader
21                     //如果父类加载器抛出ClassNotFoundException
22                     //说明父类加载器无法完成加载请求
23                 }
24 
25                 if (c == null) {
26                     // If still not found, then invoke findClass in order
27                     // to find the class.
28                     //在父类加载器无法加载的时候
29                     //再调用本身的findClass方法来进行类加载
30                     long t1 = System.nanoTime();
31                     c = findClass(name);
32 
33                     // this is the defining class loader; record the stats
34                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
35                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
36                     sun.misc.PerfCounter.getFindClasses().increment();
37                 }
38             }
39             if (resolve) {
40                 resolveClass(c);
41             }
42             return c;
43         }
44     }

双亲委派模型意义:

  • 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次
  • 保证Java程序安全稳定运行:java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

5. 类加载器间的关系

  进一步了解类加载器间的关系(并非指继承关系),主要可以分为以下4点:

  • 启动类加载器(Bootstrap ClassLoader),由C++实现,没有父类。
  • 扩展类加载器(ExtClassLoader),由Java语言实现,父类加载器为Bootstrap ClassLoader
  • 系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader
  • 自定义类加载器(Custom Classloader),父类加载器为AppClassLoader
posted @ 2019-06-26 15:48  CoderZZZ  阅读(163)  评论(0编辑  收藏  举报