大家好,我是一个爱举铁的程序员Shr。
本篇文章将详细介绍类加载器,阅读完本篇文章你可能需要20分钟。
今天讲述的内容包括:类和类加载器之间的关系,类加载器的分类,类加载器的双亲委派模型。
一、什么是类加载器?
类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为类加载器[1]。
上一篇文章类加载机制中讲的是Java虚拟机如何加载Class文件,而类加载器就是实现“如何加载”的程序代码。
二、类和类加载器
类加载器只实现类的加载动作,而类在虚拟机中还会有链接,初始化等阶段,详细阶段请查看前一篇文章Java虚拟机(二)类加载机制。
对于一个类,都会由这个类本身和加载这个类的类加载器共同确认它在Java虚拟机中的唯一性。
举一个例子,一个类被不同的类加载器加载会发生什么。
新建一个Hello类,这个类将作为被不同类加载器加载的类。
package com.shrmus.classloader;
public class Hello {
}
新建一个自定义类加载器,继承java.lang.ClassLoader抽象类,重写findClass方法。
package com.shrmus.classloader;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name){
byte[] bs = null;
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream inputStream = getClass().getResourceAsStream(fileName);
if(inputStream == null) {
return super.loadClass(name);
}
bs = new byte[inputStream.available()];
inputStream.read(bs);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, bs, 0, bs.length);
}
}
新建一个Main类。
package com.shrmus.classloader;
public class Main {
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader();
Object newInstance = myClassLoader.findClass("com.shrmus.classloader.Hello").newInstance();
System.out.println(newInstance.getClass().getClassLoader());
System.out.println(newInstance instanceof com.shrmus.classloader.Hello);
Hello hello = new com.shrmus.classloader.Hello();
System.out.println(hello.getClass().getClassLoader());
System.out.println(hello instanceof com.shrmus.classloader.Hello);
}
}
控制台打印结果:
com.shrmus.classloader.MyClassLoader@33909752
false
sun.misc.Launcher$AppClassLoader@6d06d69c
true
在Main类中,用自定义的类加载器加载Hello类并生成实例。
然后第一行打印的是新生成的实例的类加载器。
第二行打印语句通过instanceof关键字来看新生成的实例是否还属于原来的类型,控制台打印的是false。
然后再用new关键字生成一个实例,第三行打印的是这个对象的类加载器,可以看到和第一行打印出来的类加载不一样。
第四行打印的是true。