类加载器(1)

文章目录

  • 1. 类加载器
    • 1.1. 类加载机制
    • 1.2. 类加载器加载Class的步骤

1. 类加载器

类加载器负责将.class文件加载到内存,并为之生成对应的java.lang.Class对象。

类加载器负责加载所有的类,系统为所有被加载进内存的类生成一个java.lang.Class实例。
一旦一个类被加载进JVM中,同一个类就不会被再次载入了

正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其类的全限定类名(包名+类名)作为唯一标识,但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。意味着两个类加载器加载的同名类是不同的、它们所加载的类也是完全不同、互不兼容的。

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

  • BootStrap ClassLoader:根类加载器
  • Extension ClassLoader:扩展类加载器
  • System ClassLoader:系统类加载器

BootStrap ClassLoader,它负责加载Java的核心类。在Sun的JVM中,当执行java.exe命令时,使用 -Xbootclasspath 选项或 -D 选项指定 sun.boot.class.path 系统属性值可以指定加载附加的类。
该类加载器非常特殊,它不是java.lang.ClassLoader的子类,而是由JVM自身实现的

public class BootStrapTest {
     
    public static void main(String[] args) {
     
        //获得根类加载器
        sun.misc.URLClassPath bootstrapClassPath =
                sun.misc.Launcher.getBootstrapClassPath();
        System.out.println("根类加载器:" + bootstrapClassPath);
        
        //遍历、输出根类加载器加载的全部URL
        URL[] urLs = bootstrapClassPath.getURLs();
        for (URL url : urLs) {
     
            System.out.println(url.toExternalForm());
        }
    }
}

运行结果如下:
根类加载器:sun.misc.URLClassPath@65b3120a
file:/D:/Dev/Java/jdk1.8.0_181/jre/lib/resources.jar
file:/D:/Dev/Java/jdk1.8.0_181/jre/lib/rt.jar
file:/D:/Dev/Java/jdk1.8.0_181/jre/lib/sunrsasign.jar
file:/D:/Dev/Java/jdk1.8.0_181/jre/lib/jsse.jar
file:/D:/Dev/Java/jdk1.8.0_181/jre/lib/jce.jar
file:/D:/Dev/Java/jdk1.8.0_181/jre/lib/charsets.jar
file:/D:/Dev/Java/jdk1.8.0_181/jre/lib/jfr.jar
file:/D:/Dev/Java/jdk1.8.0_181/jre/classes

程序中之所以可以使用String、System这些核心类库,是因为这些核心类库都在 /jre/lib/rt.jar 包中。

Extension ClassLoader,它负责加载JRE的扩展目录(%JAVA_HOME/jre/lib/ext% 或由 java.ext.dirs 系统属性指定的目录)。

System ClassLoader,它负责在JVM启动时加载来自java命令的 -classpath 选项、java.class.path 系统属性,或 CLASSPATH 环境变量所指定的JAR包或类路径。程序可以通过 java.lang.ClassLoader#getSystemClassLoader 来获取系统类加载器。

1.1. 类加载机制

JVM的类加载机制主要有如下三种:

  • 全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
  • 父类委托(双亲委派):先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
  • 缓存机制:缓存机制将会保证所有加载过得Class都会被缓存,当程序需要使用某个Class时,类加载器首先从缓存区搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。
    这就是为什么修改了Class后,必须重新启动JVM,修改的内容才会生效。

JVM中4种类加载器的层次结构

用户类加载器
系统类加载器
扩展类加载器
根类加载器
public class ClassLoaderPropTest {
     
    public static void main(String[] args) throws Exception {
     
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系统类加载器:" + systemClassLoader);

        //获取系统类加载器的加载路径
        //该路径由CLASSPATH指定,如果未指定CLASSPATH,则系统类加载器的加载路径为当前路径
        Enumeration<URL> resources = systemClassLoader.getResources("");
        while (resources.hasMoreElements()) {
     
            System.out.println(resources.nextElement());
        }

        //获取系统类加载器的父加载器,扩展加载器
        ClassLoader extensionLoader = systemClassLoader.getParent();
        System.out.println("扩展类加载器:" + extensionLoader);
        String property = System.getProperty("java.ext.dirs");
        System.out.println(property);

        //获取扩展类加载器的父加载器
        ClassLoader parent = extensionLoader.getParent();
        System.out.println("扩展类加载器的父类加载器:" + parent);
    }
}

/*
程序运行结果:

系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
file:/D:/WorkSpace/mvnTest/target/classes/
扩展类加载器:sun.misc.Launcher$ExtClassLoader@65b3120a
D:\Dev\Java\jdk1.8.0_181\jre\lib\ext;C:\windows\Sun\Java\lib\ext
扩展类加载器的父类加载器:null
*/

根据上述示例的运行结果可以得出以下结论:

  1. 根据sun.misc.Launcher$AppClassLoader和sun.misc.Launcher$ExtClassLoader,可以得知AppClassLoaderExtClassLoaderLauncher类的内部类。通过查看源码可以得知二者有一个共同的父类java.net.URLClassLoader,且java.net.URLClassLoader的父类是java.lang.ClassLoader
  2. ExtClassLoaderAppClassLoader的父类加载器,即扩展类加载器是系统类加载器的父类加载器
  3. 对扩展类加载器进行getParent()得到 null ,这是由于根类加载器并没有继承ClassLoader抽象类,所以扩展类加载器的getParent()方法返回null。实际上,扩展类加载器的父类加载器是根类加载器,只是根类加载器并不是Java实现的

1.2. 类加载器加载Class的步骤

JVM中除根类加载器之外的所有类加载器都是java.lang.ClassLoader子类的实例。

ClassLoader类有两个关键方法:

  • protected Class loadClass(String name, boolean resolve)
    该方法是ClassLoader的入口点,根据指定名称来加载类,系统调用ClassLoader的该方法来获取指定类对应的Class对象。
  • protected Class findClass(String name) throws ClassNotFoundException
    根据根据指定名称来查找类。

既然系统通过调用loadClass方法来加载指定类,那么通过查看该方法的源码即可得到类加载器加载Class的步骤。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
     
    synchronized (getClassLoadingLock(name)) {
     
        // First, check if the class has already been loaded
        // 检测此Class是否被载入过,即缓存区中是否有此Class
        Class<?> c = findLoadedClass(name);
        if (c == null) {
     
            long t0 = System.nanoTime();
            try {
     
                if (parent != null) {
     
                    // 当父类加载器存在时,通过父类加载器加载目标类
                    c = parent.loadClass(name, false);
                } else {
     
                    // 当父类加载器不存在时,通过根类加载器加载目标类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
     
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
     
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                // 当父类加载器没有加载到目标类时,尝试寻找该类的Class文件,并从Class文件中加载目标类
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
     
            resolveClass(c);
        }
        return c;
    }
}

步骤如下:

  1. 检测此Class是否被载入过,即缓存区中是否有此Class,如果缓存区中存在则直接进入第8步,否则执行第2步
  2. 如果父类加载器不存在(当父类加载器不存在,要么parent一定是根类加载器,要么本身就是根加载器),则在直接进入第4步;
    如果父类加载器存在,则执行第3步
  3. 请求使用父类加载器去加载目标类,如果加载成功,则直接接入第8步,否则执行第5步
  4. 请求使用根类加载器来加载目标类,如果加载成功,则直接进入第8步,否则执行第5步
  5. 当前类加载器尝试寻找Class文件(从与此ClassLoader相关的类路径中寻找),如果找到则执行第6步,如果找不到则执行第7步
  6. 从文件中载入Class,成功载入后进入第8步
  7. 抛出 ClassNotFoundException 异常
  8. 返回对应的java.lang.Class对象
存在
不存在
存在
成功
不存在
成功
不成功
成功
成功
不成功
不成功
开始
缓存区中是否有此Class
返回对应的java.lang.Class对象
判断父类加载器是否存在
使用父类加载器加载目标类
使用根类加载器加载目标类
当前类加载器尝试寻找Class文件
从文件中读取Claa文件
抛出 ClassNotFoundException 异常
结束

你可能感兴趣的:(JVM)