来谈一谈类加载器。

java的class文件都是通过类加载器被加载到jvm的方法区中,供内存调用。

那么类加载器究竟是如何加载一个class文件的?

我们先要找个class文件看看,不管你们看没看懂,反正我是看不懂。

但是jvm看得懂,因为其中存在jvm可以识别的文件标示,而且能通过jvm内部的特定规则进行编译。

但是classloader不负责管这个类是否可以运行,类是否能运行,看的是Execution Engine。

那么jvm是不是只有一个classloader呢?

虚拟机自带的有三个:bootstrap、extension和AppClassLoader。

bootstrap负责的是最基础的一些类的加载。

这些类在jre/lib/rt.jar中。

可以看到,里面有个包叫java.lang,下面就有了很多我们平时常用的类。

bootstrap的作用就是加载这些类。

那么Extension加载了什么呢?

是jre/lib/ext下的所有jar包。

那我们平时自定义的那些jar包和类在哪加载呢?

自然就是最后的AppClassLoader。

同样,用户也可以自定义加载器。

那么,我们也可以经常听到类加载器有一个叫“双亲委派机制”的东西,这是什么呢?

简单地说,就是当一个类加载器收到了加载请求时,它首先不会自己加载这个类,而是把它委派给父类去完成。如果说父类没有能够成功加载这个请求,那么才会交给子类继续完成。

举个例子演示一下。

我们知道java包下有java.lang.String。

按照之前介绍的规则,它应该由bootstrap的类加载器来加载。

那如果我们手动创建java.lang包,并且创建一个String类,那么理论上因为是自己创建的类,应该由AppClassLoader创建,那事实上呢?

package java.lang;

public class String {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
    }
}

我们创建如下的类,通过Main方法看一下对应的classloader。

但是运行结果是这样的。

错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为:
   public static void main(String[] args)
否则 JavaFX 应用程序类必须扩展javafx.application.Application

这说明,类加载器并没有加载我们后面定义的那个类。

而我们也知道String类下是没有main方法的。

所以很明显,他的加载从父类开始,因为在父类已经找到了java.lang.String,所以就直接加载了这个类,而没有加载我们定义的类。

我们把包名和类名修改了试试。

package java2.lang2;

public class String2 {
    public static void main(String[] args) {
        System.out.println(String2.class.getClassLoader());
    }
}

结果就不一样了,输出的是sun.misc.Launcher$AppClassLoader@18b4aac2。

可以看到这次是通过AppClassLoader进行了加载,因为这次的包在rt.jar下不再存在。

这就是双亲委派机制。

这样做有什么好处呢?

比如加载rt.jar 包中的类 java.lang.String,不管哪个加载器加载这个类,最终都委派给了顶层加载器bootstrap进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 String对象。

这便是类加载器最基本的一些机制。