类加载器实现了类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”的这个动作~
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,通俗点说:只有两个类是同一个类加载器加载的前提下,才能比较两个类是否“相等”,否则两个类必定不相等!
举个栗子!
public class ClassLoaderTest {
public static void main(String[] args){
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try{
String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if(is == null){
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
}catch (IOException e) {
throw new ClassNotFoundException();
}
}
};
try{
Object obj = myLoader.loadClass("ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof ClassLoaderTest);
}catch (Exception a){
}
}
}
运行结果:
class ClassLoaderTest
false
在这个示例中我们发现,一个是由系统应用程序类加载器加载的,一个是由我们自己定义的类加载器加载的,虽然是同一个Class文件,但依然是两个独立的类,所以结果返回的是false。
从java开发人员的角度来看,类加载器分以下几种
- 启动类加载器:
这个类加载器主要负责将存放在<JAVA_HOME>\lib目录中或者被-Xbootclasspath参数所制定的路径中的,并且被虚拟机识别的类库加载到虚拟机内存中。(如rt.jar) - 扩展类加载器
这个类加载器主要负责加载<JAVA_HOME>\lib\ext目录中或者被java.etx.dirs系统变量所制定的路径中的所有类库,开发者可以直接使用扩展类加载器。 - 应用程序类加载器
这个类加载器主要负责加载用户类路径(ClassPath)中所指定的类库,开发者可以直接使用扩展类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器!
上图中展示的类加载器之间的这种层次关系称之为类加载器的双亲委派模型。双亲委派模型要求出来顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承的关系来是想,而是都用组合关系来复用父加载器的代码。
双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它会将这个请求传递给父类,如果父类无法加载,则再由自己去加载。这样的有一个显而易见的好处就是:java类随着它的类加载器一起具备了一种带有优先级的层次关系!
例如:java.lang.Object它存放在rt.jar中。无论哪个加载器想加载它,都会传递到启动类加载器中加载,这样,能确保不会出现多个不同的Object类(因为确定一个类是否是相同的,需要类本身以及加载它的加载器都相同!)