一、类的加载机制

在我们初学Java时,就知道,运行一个类,是通过如下几步

  1. 编译
  2. 运行

编译采用 javac命令,将Java源文件,编译成字节码文件(.class)
运行采用 java 命令,将对应的class字节码,加载入虚拟机运行
如下的测试类,即为 编译 javac TestLoader.java 运行: java TestLoader

  1. public class TestLoader {
  2. public static int a = 5;
  3. public static void main(String []args) {
  4. System.out.println("TestLoader");
  5. }
  6. }

在执行 java TestLoader 这行命令时,主要执行了如下的流程

  1. 去JRE的目录下,寻找 java.exe,java.exe 寻找 jvm.dll(c/c++的链接库),创建出java虚拟机进程
  2. java虚拟机,创建出 BootstrapClassLoader(引导类加载器)
  3. C++代码,调用 Java代码,创建 Launcher 类,Launcher类负责加载其他的 classLoader
  4. 各ClassLoader去对应的目录下,加载Class文件,通过反射,执行main方法,结束

其中,加载Class文件又细分了许多的步骤

  1. 加载。 通过 磁盘的IO,将Class文件的内容,读取到内存中。在java中,只有使用到了某个类(例如new等操作),才会去加载某个类,加载完成之后,封装成 java.lang.Class 对象。
  2. 验证。 检验Class文件的合法性,Java对于 Class文件的结构有详细的规范,详见官方文档
  3. 准备。 赋予静态变量初始值,例如上述的例子中,将static变量a赋值为默认值0
  4. 解析。 将 符号引用 转换成 直接引用(例如将main这个符号,转化为数据所在的指针),即静态链接的过程(在类加载阶段就完成)。(动态链接在运行期间才生效)
  5. 初始化。 赋予静态变量真实值,并执行static代码块,例如上述的例子中,将static变量a赋值为用户设置的值 5

类加载之后,放入到JVM中的方法区中,在方法区中保存着

  1. 运行时常量池,
  2. 类型信息。 这个类,对应的类型是什么,如上为 TestLoader类型
  3. 方法信息。 这个类,类中有多少方法,如上只有main方法
  4. 类加载器引用。 这个类,被哪个类加载器所加载的。 是AppClassLoader,还是ExtClassLoader?
  5. 对应的class实例引用。 对应的Class对象

类的懒加载(使用到某个类之后,才会加载某个类)

当并没有执行 new B() 时,并不会打印出 B 类中的静态代码块,说明,此时B类并没有被加载。

  1. public class LazyLoad {
  2. public static void main(String[] args) {
  3. new A();
  4. B b = null;
  5. // B b = new B();
  6. }
  7. }
  8. class A {
  9. static {
  10. System.out.println("class A loading");
  11. }
  12. public A() {
  13. System.out.println("init A");
  14. }
  15. }
  16. class B {
  17. static {
  18. System.out.println("class B loading");
  19. }
  20. public B() {
  21. System.out.println("init B");
  22. }
  23. }

输出结果如下

  1. class A loading
  2. init A

二、类加载器

Java中的类加载器主要有如下几种

  1. BootstrapClassLoader (启动类加载器),加载JVM的核心库,例如rt.jar
  2. ExtClassLoader (扩展类加载器), 加载扩展Jar包,例如 jre/lib/ext 下的jar包
  3. AppClassLoader (应用程序类加载器),负责加载当前classpath路径下的类,自己写的类,主要靠AppClassLoader来加载
  4. 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);

  1. 打印结果
  2. ```java
  3. null // BootstrapClassLoader,启动类加载器,由于C++加载,所以打印不出来
  4. sun.misc.Launcher$ExtClassLoader // 扩展类加载器,主要加载 ext
  5. sun.misc.Launcher$AppClassLoader // 应用程序加载器,加载自定义的类,例如 TestClassLoader
  6. the bootstrapLoader : null
  7. the extClassloader : sun.misc.Launcher$ExtClassLoader@12a3a380
  8. the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2

2. 三种加载器对应的加载路径

  1. System.out.println("bootstrapLoader加载以下文件:");
  2. URL[] urls = Launcher.getBootstrapClassPath().getURLs();
  3. for (int i = 0; i < urls.length; i++) {
  4. System.out.println(urls[i]);
  5. }
  6. System.out.println();
  7. System.out.println("extClassloader加载以下文件:");
  8. System.out.println(System.getProperty("java.ext.dirs"));
  9. System.out.println();
  10. System.out.println("appClassLoader加载以下文件:");
  11. System.out.println(System.getProperty("java.class.path"));

打印结果

  1. bootstrapLoader加载以下文件:
  2. file:/home/ifan/software/java/jdk1.8/jre/lib/resources.jar
  3. file:/home/ifan/software/java/jdk1.8/jre/lib/rt.jar
  4. file:/home/ifan/software/java/jdk1.8/jre/lib/sunrsasign.jar
  5. file:/home/ifan/software/java/jdk1.8/jre/lib/jsse.jar
  6. file:/home/ifan/software/java/jdk1.8/jre/lib/jce.jar
  7. file:/home/ifan/software/java/jdk1.8/jre/lib/charsets.jar
  8. file:/home/ifan/software/java/jdk1.8/jre/lib/jfr.jar
  9. file:/home/ifan/software/java/jdk1.8/jre/classes
  10. extClassloader加载以下文件:
  11. /home/ifan/software/java/jdk1.8/jre/lib/ext:/usr/java/packages/lib/ext
  12. appClassLoader加载以下文件:
  13. /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

  1. public Launcher() {
  2. Launcher.ExtClassLoader var1;
  3. try {
  4. // 创建ExtClassLoader,没有传递任何参数,即它的父加载器为null,即为 BootstrapClassLoader
  5. var1 = Launcher.ExtClassLoader.getExtClassLoader();
  6. } catch (IOException var10) {
  7. throw new InternalError("Could not create extension class loader", var10);
  8. }
  9. try {
  10. // 创建AppClassLoader,将var1(ExtClassLoader),设置为AppClassLoader的父加载器
  11. this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
  12. } catch (IOException var9) {
  13. throw new InternalError("Could not create application class loader", var9);
  14. }
  15. // 设置默认的加载器,是AppClassLoader
  16. Thread.currentThread().setContextClassLoader(this.loader);
  17. // 进行一些,安全校验
  18. String var2 = System.getProperty("java.security.manager");
  19. if (var2 != null) {
  20. SecurityManager var3 = null;
  21. if (!"".equals(var2) && !"default".equals(var2)) {
  22. try {
  23. var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
  24. } catch (IllegalAccessException var5) {
  25. } catch (InstantiationException var6) {
  26. } catch (ClassNotFoundException var7) {
  27. } catch (ClassCastException var8) {
  28. }
  29. } else {
  30. var3 = new SecurityManager();
  31. }
  32. if (var3 == null) {
  33. throw new InternalError("Could not create SecurityManager: " + var2);
  34. }
  35. System.setSecurityManager(var3);
  36. }
  37. }

从上面的创建ExtClassLoader,以及创建AppClassLoader中,创建的时候传入了对应的父加载器(null/ExtClassLoader),就可以看出,这三个加载器的关系如下
再看ClassLoader#loadClass,加载类时

  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. Class<?> c = findLoadedClass(name);
  7. if (c == null) {
  8. long t0 = System.nanoTime();
  9. try {
  10. if (parent != null) {
  11. c = parent.loadClass(name, false);
  12. } else {
  13. c = findBootstrapClassOrNull(name);
  14. }
  15. } catch (ClassNotFoundException e) {
  16. // ClassNotFoundException thrown if class not found
  17. // from the non-null parent class loader
  18. }
  19. if (c == null) {
  20. // If still not found, then invoke findClass in order
  21. // to find the class.
  22. long t1 = System.nanoTime();
  23. c = findClass(name);
  24. // this is the defining class loader; record the stats
  25. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  26. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  27. sun.misc.PerfCounter.getFindClasses().increment();
  28. }
  29. }
  30. if (resolve) {
  31. resolveClass(c);
  32. }
  33. return c;
  34. }
  35. }

从上面这段代码就可以看出,loadClass的主要流程如下

  1. getClassLoadingLock ,利用ConcurrentHashMap进行加锁
  2. findLoadedClass 寻找已经加载过的类 (底层调用native方法,在C++层次维护),如果已经加载过了,直接返回,如果没有加载过,走3
  3. 尝试进行加载
    1. 如果有parent父加载器,那么,调用parent.loadClass,让父加载器去加载
    2. 如果没有父加载器,说明到了最顶层,调用native方法,去加载类
  4. 如果父加载器,都无法加载到这个类,执行 findClass(name) ,自己来找,将会去此类加载器对应的路径下,寻找类,如果到了最底层(AppClassLoader),还找不到这个类的话,那么将会抛出ClassNotFountException。

    从以上的分析中可以得出,Java的类加载是依照双亲委派机制来进行加载的,先有父加载器进行加载,父加载器中加载不到的话,再有自己来进行加载。

    那为什么Java要使用双亲委派机制的方式来进行加载类呢?

    主要原因有两点;

  5. 沙箱安全机制: 避免用户自定义包名类名相同的类,来篡改Java的核心类库

  6. 避免类的重复加载: 保证在内存中,只有同一个类信息,不需要每个加载器都去加载一遍