前面已经介绍了JAVA类加载的完整过程,现在就说一下类加载用到的类加载器。在类加载的第一步也就是加载过程中,首先要获取到这个类的.class文件也就是二进制字节流,这个获取二进制字节流的步骤就是类加载器来完成的。在JAVA虚拟机中,除了启动类加载器(BootstrapClassLoader)是用C++实现之外,其它所有的类加载器都是用JAVA实现的。各种类加载器的关系图如下:
启动类加载器所加载的类是存放在$JAVA_HOME\lib下的所有类,拓展类加载器加载$JAVA_HOME\lib\ext中的类,应用程序类加载器加载用户路径下的类。上图被称为双亲委派模型,意思是当一个类加载器收到类加载请求时先将这个请求委派给父类加载器,父类加载器无法加载时子类加载器才会去加载这个类。为什么要设置这样一个层次关系呢?因为对于同一个类来说,如果加载他们的类加载器是不一样的,那么这两个类就是“不相等”的,包括instance of运算的结果和equals方法调用的结果。双亲委派模型就能够保证顶级的类例如Object就是顶端的加载器加载的,有利于程序的安全稳定。
只是这么说利于程序的稳定运行有点无力了,所以我对双亲委派模型举个例子:某个类需要应用程序类加载器来加载,应用程序类加载器就会把加载请求委托给拓展类程序类加载器,拓展类加载器收到请求自己也不会加载而是把加载请求委托给启动类加载器。这时候启动类加载器就开始在lib中搜索,看看JDK源码里有没有这个类,如果有就自己加载,如果没有再托给下级也就是拓展类加载器,以此类推。再说得具体一点就是,我自己写了个叫String的类,和JDK的String重名了,里面有一些恶意代码,但是这并不会导致JDK源码被篡改,因为启动类加载器早就加载了正版的String类,收到加载我的盗版String类的请求时,由于类加载的缓存机制,会直接拿走缓存中正版String类的字节码。这样就保证了程序的安全。