一、类的加载机制
在我们初学Java时,就知道,运行一个类,是通过如下几步
- 编译
- 运行
编译采用 javac命令,将Java源文件,编译成字节码文件(.class)
运行采用 java 命令,将对应的class字节码,加载入虚拟机运行
如下的测试类,即为 编译 javac TestLoader.java 运行: java TestLoader
public class TestLoader {public static int a = 5;public static void main(String []args) {System.out.println("TestLoader");}}
在执行 java TestLoader 这行命令时,主要执行了如下的流程
- 去JRE的目录下,寻找 java.exe,java.exe 寻找 jvm.dll(c/c++的链接库),创建出java虚拟机进程
- java虚拟机,创建出 BootstrapClassLoader(引导类加载器)
- C++代码,调用 Java代码,创建 Launcher 类,Launcher类负责加载其他的 classLoader
- 各ClassLoader去对应的目录下,加载Class文件,通过反射,执行main方法,结束
其中,加载Class文件又细分了许多的步骤
- 加载。 通过 磁盘的IO,将Class文件的内容,读取到内存中。在java中,只有使用到了某个类(例如new等操作),才会去加载某个类,加载完成之后,封装成
java.lang.Class对象。 - 验证。 检验Class文件的合法性,Java对于 Class文件的结构有详细的规范,详见官方文档
- 准备。 赋予静态变量初始值,例如上述的例子中,将static变量a赋值为默认值0
- 解析。 将 符号引用 转换成 直接引用(例如将main这个符号,转化为数据所在的指针),即静态链接的过程(在类加载阶段就完成)。(动态链接在运行期间才生效)
- 初始化。 赋予静态变量真实值,并执行static代码块,例如上述的例子中,将static变量a赋值为用户设置的值 5
类加载之后,放入到JVM中的方法区中,在方法区中保存着
- 运行时常量池,
- 类型信息。 这个类,对应的类型是什么,如上为 TestLoader类型
- 方法信息。 这个类,类中有多少方法,如上只有main方法
- 类加载器引用。 这个类,被哪个类加载器所加载的。 是AppClassLoader,还是ExtClassLoader?
- 对应的class实例引用。 对应的Class对象
类的懒加载(使用到某个类之后,才会加载某个类)
当并没有执行 new B() 时,并不会打印出 B 类中的静态代码块,说明,此时B类并没有被加载。
public class LazyLoad {public static void main(String[] args) {new A();B b = null;// B b = new B();}}class A {static {System.out.println("class A loading");}public A() {System.out.println("init A");}}class B {static {System.out.println("class B loading");}public B() {System.out.println("init B");}}
输出结果如下
class A loadinginit A
二、类加载器
Java中的类加载器主要有如下几种
- BootstrapClassLoader (启动类加载器),加载JVM的核心库,例如rt.jar
- ExtClassLoader (扩展类加载器), 加载扩展Jar包,例如 jre/lib/ext 下的jar包
- AppClassLoader (应用程序类加载器),负责加载当前classpath路径下的类,自己写的类,主要靠AppClassLoader来加载
- CustomClassLoader (自定义ClassLoader),根据自定义的规则,加载指定路径下的类
类加载器验证与使用
1. 验证3种加载器
```java System.out.println(String.class.getClassLoader()); System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(TestClassLoader.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);
打印结果```javanull // BootstrapClassLoader,启动类加载器,由于C++加载,所以打印不出来sun.misc.Launcher$ExtClassLoader // 扩展类加载器,主要加载 extsun.misc.Launcher$AppClassLoader // 应用程序加载器,加载自定义的类,例如 TestClassLoaderthe bootstrapLoader : nullthe extClassloader : sun.misc.Launcher$ExtClassLoader@12a3a380the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
2. 三种加载器对应的加载路径
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();System.out.println("appClassLoader加载以下文件:");System.out.println(System.getProperty("java.class.path"));
打印结果
bootstrapLoader加载以下文件:file:/home/ifan/software/java/jdk1.8/jre/lib/resources.jarfile:/home/ifan/software/java/jdk1.8/jre/lib/rt.jarfile:/home/ifan/software/java/jdk1.8/jre/lib/sunrsasign.jarfile:/home/ifan/software/java/jdk1.8/jre/lib/jsse.jarfile:/home/ifan/software/java/jdk1.8/jre/lib/jce.jarfile:/home/ifan/software/java/jdk1.8/jre/lib/charsets.jarfile:/home/ifan/software/java/jdk1.8/jre/lib/jfr.jarfile:/home/ifan/software/java/jdk1.8/jre/classesextClassloader加载以下文件:/home/ifan/software/java/jdk1.8/jre/lib/ext:/usr/java/packages/lib/extappClassLoader加载以下文件:/home/ifan/software/java/jdk1.8/jre/lib/charsets.jar:/home/ifan/software/java/jdk1.8/jre/lib/deploy.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/cldrdata.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/dnsns.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/jaccess.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/jfxrt.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/localedata.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/nashorn.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/sunec.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/sunjce_provider.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/sunpkcs11.jar:/home/ifan/software/java/jdk1.8/jre/lib/ext/zipfs.jar:/home/ifan/software/java/jdk1.8/jre/lib/javaws.jar:/home/ifan/software/java/jdk1.8/jre/lib/jce.jar:/home/ifan/software/java/jdk1.8/jre/lib/jfr.jar:/home/ifan/software/java/jdk1.8/jre/lib/jfxswt.jar:/home/ifan/software/java/jdk1.8/jre/lib/jsse.jar:/home/ifan/software/java/jdk1.8/jre/lib/management-agent.jar:/home/ifan/software/java/jdk1.8/jre/lib/plugin.jar:/home/ifan/software/java/jdk1.8/jre/lib/resources.jar:/home/ifan/software/java/jdk1.8/jre/lib/rt.jar:/home/ifan/workspace/idea-workspace/juc/out/production/juc:/home/ifan/software/java/idea/lib/idea_rt.jar
类加载器的加载过程
在本文刚开始时,即说明了,C++将会调用Java,创建Launcher类,Launcher类负责加载其他的 classLoader
public Launcher() {Launcher.ExtClassLoader var1;try {// 创建ExtClassLoader,没有传递任何参数,即它的父加载器为null,即为 BootstrapClassLoadervar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {// 创建AppClassLoader,将var1(ExtClassLoader),设置为AppClassLoader的父加载器this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}// 设置默认的加载器,是AppClassLoaderThread.currentThread().setContextClassLoader(this.loader);// 进行一些,安全校验String var2 = System.getProperty("java.security.manager");if (var2 != null) {SecurityManager var3 = null;if (!"".equals(var2) && !"default".equals(var2)) {try {var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();} catch (IllegalAccessException var5) {} catch (InstantiationException var6) {} catch (ClassNotFoundException var7) {} catch (ClassCastException var8) {}} else {var3 = new SecurityManager();}if (var3 == null) {throw new InternalError("Could not create SecurityManager: " + var2);}System.setSecurityManager(var3);}}
从上面的创建ExtClassLoader,以及创建AppClassLoader中,创建的时候传入了对应的父加载器(null/ExtClassLoader),就可以看出,这三个加载器的关系如下
再看ClassLoader#loadClass,加载类时
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}
从上面这段代码就可以看出,loadClass的主要流程如下
- getClassLoadingLock ,利用ConcurrentHashMap进行加锁
- findLoadedClass 寻找已经加载过的类 (底层调用native方法,在C++层次维护),如果已经加载过了,直接返回,如果没有加载过,走3
- 尝试进行加载
- 如果有parent父加载器,那么,调用parent.loadClass,让父加载器去加载
- 如果没有父加载器,说明到了最顶层,调用native方法,去加载类
如果父加载器,都无法加载到这个类,执行
findClass(name),自己来找,将会去此类加载器对应的路径下,寻找类,如果到了最底层(AppClassLoader),还找不到这个类的话,那么将会抛出ClassNotFountException。从以上的分析中可以得出,Java的类加载是依照双亲委派机制来进行加载的,先有父加载器进行加载,父加载器中加载不到的话,再有自己来进行加载。
那为什么Java要使用双亲委派机制的方式来进行加载类呢?
主要原因有两点;
沙箱安全机制: 避免用户自定义包名类名相同的类,来篡改Java的核心类库
- 避免类的重复加载: 保证在内存中,只有同一个类信息,不需要每个加载器都去加载一遍
