Java类加载机制详解

2020-09-03 10:29:04 浏览数 (1)

1.概述

Java类加载器负责加载所有的类,系统会为所有被载入内存的类生成一个java.lang.Class实例。对于同一个类,一旦被加载如内存中,就不会被再次加载。JVM使用一个类的权限的类名和该类的加载器唯一地标识一个类。因此即使两个类的包名、类名完全相同,但是使用不同的类加载器加载,这两个类也会被认为是不同的。 当程序首次使用某个类时,如果该类还未被加载,则系统会通过以下三个步骤加载该类:

  1. 加载:查找和载入Class字节码文件
  2. 连接:执行校验、准备和解析三个步骤 2.1校验:检查载入的Class文件的正确性,并和其他类协调一致 2.2准备:为类的静态变量分配内存,并设置默认初始值 2.3解析:将类的符号引用转换成直接引用
  3. 初始化:对类的静态变量、静态代码块执行初始化工作

有时,也将上述三个步骤统称为类的加载。

2.类加载器的层次结构

当JVM启动时,会形成由三个类加载器组成的初始化类加载器层次结构:

  1. Bootstrap CLassLoader:根类加载器,也称为引导类加载器,负责加载Java的核心类,如JRE目标下的tr.jar、charsets.jar等。根类加载器并不是ClassLoader的子类,它采用C 编写,在Java程序中无法获得。
  2. Extension ClassLoader:扩展类加载器,它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext)下的jar包。
  3. Application ClassLoader:应用类加载器,也称为System ClassLoader(系统类加载器),主要负责加载classpath路径下的jar包。

三个类加载器之间存在父子层级关系,即Bootstrap ClassLoader是Extension ClassLoader的父类加载器,Extension ClassLoader是Application ClassLoader的父类加载器。这里类加载器的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。 默认情况下,Java程序使用Application ClassLoader加载一个类。示例程序如下:

代码语言:javascript复制
public class CLassLoaderTest {

    public static void main(String[] args) {
        //获取当前线程下的ClassLoader
        ClassLoader current = Thread.currentThread().getContextClassLoader();
        System.out.println("current:"   current);

        //获取父类加载器
        System.out.println("parent:"   current.getParent());
        System.out.println("grandparent:"   current.getParent().getParent());

    }

}

输出结果:

代码语言:javascript复制
current:sun.misc.Launcher$AppClassLoader@56e88e24
parent:sun.misc.Launcher$ExtClassLoader@3dcc0a0f
grandparent:null

由此可见,Java程序默认采用Application ClassLoader,Extension ClassLoader为Application ClassLoader的父类加载器。由于Bootstrap CLassLoader由C 编写,在程序中无法获取,所以返回null。

3.类加载机制

JVM采用如下三种类加载机制:

  1. 全盘委托:当一个类加载器负责加载某个类时,该类所依赖和引用的其他类也将由该类加载器负责加载,除非显式指定另一个类加载器。
  2. 父类委托:加载一个类时,首先会让其父类加载器进行加载,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载。这一机制可以保证类加载的安全性。
  3. 缓存机制:JVM会缓存所有已经加载过的类,当程序使用某个类时,会首先在缓存中搜寻该类,只有缓存中不存在时才会加载。这也是为什么修改了一个类后,只有重启JVM才会生效。

为了更好地说明JVM的类加载机制,对ClassLoader的loadClass()方法的源码分析如下:

代码语言:javascript复制
protected Class<?> loadClass(String name, boolean resolve)
                throws ClassNotFoundException
            {
                synchronized (getClassLoadingLock(name)) {
                    //首先,检查该类是否已经被加载,如果已经加载过,则直接返回该Class(缓存机制)
                    Class c = findLoadedClass(name);

                    if (c == null) {
                        long t0 = System.nanoTime();
                        try {
                            //如果该类未被加载,首先尝试获取父加载器,如果父加载器不为null,则使用父加载器进行加载(父类委托机制)
                            if (parent != null) {
                                c = parent.loadClass(name, false);
                            } else {

                                //如果父加载器为空,说明当前加载器为ExtensionClassLoader 或 BootstrapCLassLoader,则使用根类加载器进行加载
                                c = findBootstrapClassOrNull(name);
                            }
                        } catch (ClassNotFoundException e) {
                            //如果使用根类加载器加载失败,则抛出 ClassNotFoundException
                        }

                        if (c == null) {

                            //如果父加载器未能成功加载,则使用当前的类加载器进行加载
                            long t1 = System.nanoTime();
                            c = findClass(name);

                            sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                            sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                            sun.misc.PerfCounter.getFindClasses().increment();
                        }
                    }
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
            }

0 人点赞