JAVA 类加载原理

类运行全过程

在日常开发过程中,我们都会用到许许多多的类,跟我们开发息息相关,而了解他的加载原理那是重中之重;

前提知识点

前提知识点很重要请仔细阅读,带着知识点跟着下方案例走。

首先大家请看这个段代码

public class Math {
    public static final int initData = 666;
    public static User user = new User();

    public int compute() { //一个方法对应一块栈帧内存区域
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }
    
    public static void main(String[] args) {
        Math math = new Math();
        math.compute();
    }
}

这是一段非常简单的代码,new Maht对象,调用compute()方法。
我们来简单的了解下它的加载流程
在这里插入图片描述
是不是很难理解,不着急,往下看

类加载器

我们先来看看几种类加载器

1、引导类加载器(BootstarpLoader):由C++实现的加载器,负责加载支撑JVM运行位于JRE的lib目录下核心Java类,例如:rt.jar、charsets.jar等;
2、扩展类加载器(ExtClassLoader):负责加载支撑JVM运行位于JRE的ext扩展目录中的Jar类包;
3、应用程序加载器(AppClassLoader):负责加载ClassPath路径下的类包,说白话就是加载你自己写的类;
4、自定义类加载器:负责加载用户自定义路径下的类包

Math对象加载流程可离不开上述几种加载器。

每种加载器都负责加载不同包路径下的类,有各自的分工,我们先来看看类加载器的核心类Launcher

在这里插入图片描述
Launcher中装载java类加载器ExtClassLoaderAppClassLoader,大致结构如下

public class Launcher {
	static class AppClassLoader extends URLClassLoader {
 	}
 	static class ExtClassLoader extends URLClassLoader {
 	}
}

可以看出这两个类加载器存放在Launcher中的两个内部类,他们公共继承了
URLClassLoaderURLClassLoader 最终继承了ClassLoader
在这里插入图片描述
BootstarpLoader,他是由C++所实现的类,由C++发起调用,我们在jdk源码中是看不到的,感兴趣的可以去看看HotSpot源码。

我们大致了解加载器的基本结构后,再来看看双亲委派机制

什么是双亲委派机制

在这里插入图片描述
双亲委派机制是指,加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在委托子类加载器寻找,直至找到该类
而JDK类加载中就使用了双亲委派机制,比如我们的Math类,最先会找应用程序类加载器加载(AppClassLoader),应用程序类加载器会先委托扩展类加载器(ExtClassLoader)加载,扩展类加载器再委托引导类加载器(BootstarpLoader),顶层引导类加载器在自己的类加载路径里找了半天没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。。

我们来看看下面这个案例:

public class TestJDKClassLoader {
    public static void main(String[] args) {
   		//获取String类的加载器
        System.out.println(String.class.getClassLoader());
        //获取扩展类加载器
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        //获取Target目录下类加载器,也就是自己写的类
        
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());

        System.out.println();
        //获取各个加载器的父加载器
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        ClassLoader extClassloader = appClassLoader.getParent();
        ClassLoader bootstrapLoader = extClassloader.getParent();
        System.out.println("the bootstrapLoader : " + bootstrapLoader);
        System.out.println("the extClassloader : " + extClassloader);
        System.out.println("the appClassLoader : " + appClassLoader);

        System.out.println();
        System.out.println("bootstrapLoader加载以下文件:");
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (int i = 0; i < urls.length; i++) {
            System.out.println(urls[i]);
        }
        System.out.println();
        System.out.println("extClassloader加载以下文件:");
        System.out.println(System.getProperty("java.ext.dirs"));

        System.out.println();
        // appClassLoader 他会打印所有的 加载类路径 但是 会去读取不会去加载
        System.out.println("appClassLoader加载以下文件:");
        System.out.println(System.getProperty("java.class.path"));

    }

}

// ------------------------输出内容如下----------------------------
//BootstrapLoader 是C++实现 所以输出为null
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader

the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@3764951d
the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc

bootstrapLoader加载以下文件:
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/resources.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/rt.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/sunrsasign.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jsse.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jce.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/charsets.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jfr.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/classes

extClassloader加载以下文件:
D:\dev\Java\jdk1.8.0_45\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

appClassLoader加载以下文件:
D:\dev\Java\jdk1.8.0_45\jre\lib\charsets.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\deploy.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\javaws.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jce.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfr.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jsse.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\management-agent.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\plugin.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\resources.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\rt.jar;D:\ideaProjects\project-all\target\classes;C:\Users\zhuge\.m2\repository\org\apache\zookeeper\zookeeper\3.4.12\zookeeper-3.4.12.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;C:\Users\zhuge\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\zhuge\.m2\repository\jline\jline\0.9.94\jline-0.9.94.jar;C:\Users\zhuge\.m2\repository\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;C:\Users\zhuge\.m2\repository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;C:\Users\zhuge\.m2\repository\com\google\guava\guava\22.0\guava-22.0.jar;C:\Users\zhuge\.m2\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305-1.3.9.jar;C:\Users\zhuge\.m2\repository\com\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations-2.0.18.jar;C:\Users\zhuge\.m2\repository\com\google\j2objc\j2objc-annotations\1.1\j2objc-annotations-1.1.jar;C:\Users\zhuge\.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.14\animal-sniffer-annotations-1.14.jar;D:\dev\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar

现在大致了解什么是类加载器与双亲委派机制 后我们来进入实战。

操作案例

接下来进入源码
为了方便快速理解,从核心方法,由核心类慢慢扩展理解
请看Launcher 核心类中的loadClass()方法

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
   int var3 = var1.lastIndexOf(46);
   if (var3 != -1) {
       SecurityManager var4 = System.getSecurityManager();
       if (var4 != null) {
           var4.checkPackageAccess(var1.substring(0, var3));
       }
   }

   if (this.ucp.knownToNotExist(var1)) {
       Class var5 = this.findLoadedClass(var1);
       if (var5 != null) {
           if (var2) {
               this.resolveClass(var5);
           }

           return var5;
       } else {
           throw new ClassNotFoundException(var1);
       }
   } else {
       return super.loadClass(var1, var2);
   }
}

首先这个方法有两个参数String var1, boolean var2 ,var2这个参数不做考虑,而var1他是我们加载类的包名路径;
我们用Debug打个断点看看,右击给断点设置条件var1.equals("com.example.demo03.demo.Math")
在这里插入图片描述
我们已经把Math类拦截下来了,
前面有多个if语句,都是类加载的初始化过程,他最后调用了父类(ClassLoader)super.loadClass(var1, var2)方法,我们直接跳进去。
父类ClassLoader:
这个方法非常重要了,此方法实现了双亲委派机制。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                //如果父加载器不为空 使用父加载器(此处实现了双亲委派机制)
                    c = parent.loadClass(name, false);
                } else {
                //findBootstrapClassOrNull 为native方法,根据name加载类
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
			//如果c为null,即加载失败
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                //委派自己子类找,最终他会调用URlClassLoader类的findClass方法
                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;
    }
}

我们可以看到核心逻辑代码都被synchronized包裹着,加上了getClassLoadingLock(name)锁,所以他是线程安全的;
在这里插入图片描述
我们再往下看,调用findLoadedClass(name)方法找到此加载类,如果c=null也就是Math没有加载,那他必定会进入if语句
在这里插入图片描述
进入if语句后,可以看到try/catch代码块

	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
	}

如果parent不为空,则使用parent调用加载类方法;
parent 是什么,怎么来的?
parent 其实就是ClassLoader中的一个参数,里面装着类加载器它的父加载器
在这里插入图片描述
看看他是如何赋值的,看下Launcher.class类的构造方法
在这里插入图片描述
调用了两个类加载器的初始化方法,点进去看看
ExtClassLoader:
在这里插入图片描述
再次进去,注意(classLoader)为null
在这里插入图片描述
可以看到他调用父类的方法,点到最后,会进入最终父类ClassLoader方法
在这里插入图片描述
我们可以知道ExtClassLoader 他的parent是null,按照双亲委派他应该为bootstarpLoader,因为他是由C++实现所以他为null,而AppClassLoder亦是如此,只不过他的parent放入的是ExtClassLoader
在这里插入图片描述
上述方法都是由C++来调用的所以debug断点是拦截不到的

回到loadClass方法

首先,调用了findLoadedClass(name)方法,查找该类是否已被加载吗,如果为空,则使用自己父加载器parent.loadClass(name, false)再次调用此方法,如果parent为空,则调用findBootstrapClassOrNull(name)方法加载类。
如果未找到,则会调用findClass(name),委派子加载器加载,最终他会调用URLClassLoader findClass(final String name)
URLClassLoader:

protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
            //拼接类名全路径
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                        //记载类
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

会调用 defineClass方法,而此方法是本地方法无法看到,了解大致流程就可以了
在这里插入图片描述

总结

1、初始化ExtClassLoader 与 AppClassLoder 给 parent 赋值(实现双亲委派前提);
2、调用核心方法loadclass,使用parent调用自己递归,从而实现双亲委派;
3、最终向下委派,调用findClass加载类;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值