类加载器双亲委托机制实例深度剖析
java.lang.ClassLoader protected Class<?> loadClass(String name , boolean resolve) throws ClassNotFoundException
Loads the class with the specified binary name. The default implementation of this method searches for classes in the following order:
① Invoke findLoadedClass(String) to check if the class has already been loaded.
② Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.
③ Invoke the findClass(String) method to find the class. 使用指定的二进制名称加载类。此方法的默认实现按以下顺序搜索类:
①调用findLoadedClass(String)检查类是否已加载。
②在父类加载器上调用loadClass方法。如果父级为空,则使用虚拟机的内置类加载器。
③调用findClass(String)方法来查找类。
If the class was found using the above steps, and the resolve flag is true, this method will then invoke the resolveClass(Class) method on the resulting Class object.
如果使用上述步骤找到该类,并且resolve标志为true,则此方法将对生成的类对象调用resolveClass(Class)方法。
Subclasses of ClassLoader are encouraged to override findClass(String), rather than this method.
Unless overridden, this method synchronizes on the result of getClassLoadingLock method during the entire class loading process. 鼓励类加载器的子类重写findClass(String),而不是此方法。
除非重写,否则在整个类加载过程中,此方法将同步getClassLoadingLock方法的结果。
Parameters:
name - The binary name of the class
resolve - If true then resolve the class
Returns:
The resulting Class object
Throws:
ClassNotFoundException - If the class could not be found
献上源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
- 对15节的自定义类加载继续分析:
- 在(1)、(2)、(3)处分别加了打印语句,最终发现 (1) 、(2)没有被执行,只有(3)被执行。因为MyTest16 loader1 = new MyTest16(“loader1”);,是一个参数的构造函数, 这样 “将系统类加载器当做该类加载器的父加载器”,同时,系统类加载器又可以加载 MyTest16,所以,最终打印输出的语句中是:系统类加载,而不是我们自定义的类加载器。
- 究其原因,根据类加载器的双亲委托机制,MyTest16(“loader1”)一定不是loader1这个类加载器率先尝试加载,而是委托给他的父亲尝试加载, 而loader1的父亲就是 “系统类加载器”。
- 若有一个类加载器能够成功加载Test类,那么这个类加载器被称为定义类加载器,所有能成功返回Class对象应用的类加载器(包括自定义类加载器)都被称为初始类加载器。【这里的定义类加载器是:系统类加载器,初始类加载器为:系统类加载器和自定类加载(loader1)】
/**
* 自定义类加载器
*/
public class MyTest16 extends ClassLoader{
private String classLoaderName; //标示性的属性,类加载器的名字
private final String fileExtension = ".class"; //每一次中磁盘上读取的文件的扩展名
public MyTest16(String classLoaderName){
super(); //将系统类加载器当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
// 该方法的前提是已经有了parent这样一个加载器
public MyTest16(ClassLoader parent, String classLoaderName){
super(parent); //显示执行该类加载器的父加载器(parent)
this.classLoaderName = classLoaderName;
}
@Override
public String toString() {
return "[" + this.classLoaderName + "]";
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
System.out.println("findClass invoked:"+className); //(1)
System.out.println("class loader name:"+this.classLoaderName); //(2)
byte[] data = this.loadClassData(className);
return this.defineClass(className,data,0,data.length); //最终返回一个字节class对象
}
/*
通过类的名字(className),把对应的文件的名字找到,并以输入输出流的形式最后返回一个字节数组,这个
字节数组就是从class文件中读取的二进制信息。
*/
private byte[] loadClassData(String className){
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
this.classLoaderName = this.classLoaderName.replace(".","/");
is = new FileInputStream(new File(className + this.fileExtension));
int ch;
while (-1 != (ch=is.read())){
baos.write(ch);
}
data = baos.toByteArray();
}catch (Exception ex){
ex.printStackTrace();
}finally {
try {
baos.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void test(ClassLoader classLoader) throws Exception {
//1.下面这个调用的loadClass方法,其实调用的还是ClassLoader类中的方法,
//2.loadClass方法回去调用我们上面所重写的 findClass方法,根据二进制名字寻找
//class对象(这里是MyTest10类)。 【这也是自定义类最关键的一环,重写findClass方法】
Class<?> clazz = classLoader.loadClass("Jvm.MyTest10");
Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader()); //(3)
}
public static void main(String[] args) throws Exception {
MyTest16 loader1 = new MyTest16("loader1");
test(loader1);
}
}
运行结果:
MyTest10 static block
Jvm.MyTest10@6d6f6e28
sun.misc.Launcher$AppClassLoader@18b4aac2
1. 使用自定义的类加载器,加载class文件:
- 首先将MyTest10的class文件,在桌面创建一个文件夹Jvm放里面,然后删除编译生成的MyTest10.class文件。当程序运行时,loader1加载器(自定义加载器)首先委托双亲(系统类加载器)去加载,系统类加载器获取找classpath目录下对应的class文件,但是,因为class文件删除了,所以加载不成功,然后往上找双亲直到根类加载器,还是加载不成功,然后返回到loader1类加载器,由loader1去加载MyTest10.class
- ★★如果★★编译生成的MyTest10.class文件不删除,那么将由系统类加载器去加载该class文件,【原因:有双亲委托机制,loader1类加载器会调用父类系统类加载器去尝试加载classpath文件夹下的class文件,这个时候,因为存在在classpath下存在class文件,所以可以加载。而,不会去加载桌面上的class文件】
/**
* 自定义类加载器
*/
public class MyTest16 extends ClassLoader{
private String classLoaderName;
private String path;
private final String fileExtension = ".class";
public MyTest16(String classLoaderName){
super(); //将系统类加载器当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public MyTest16(ClassLoader parent, String classLoaderName){
super(parent); //显示执行该类加载器的父加载器(parent)
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
@Override
public String toString() {
return "[" + this.classLoaderName + "]";
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
System.out.println("findClass invoked:"+className);
System.out.println("class loader name:"+this.classLoaderName);
byte[] data = this.loadClassData(className);
return this.defineClass(className,data,0,data.length); //最终返回一个class对象
}
private byte[] loadClassData(String className){
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
className = className.replace(".","\\");
try {
is = new FileInputStream(new File(this.path + className + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch;
while (-1 != (ch = is.read())){
baos.write(ch);
}
data = baos.toByteArray();
}catch (Exception ex){
ex.printStackTrace();
}finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
MyTest16 loader1 = new MyTest16("loader1");
loader1.setPath("C:\\Users\\admin\\Desktop\\");
//这里是将MyTest10的class文件,在桌面创建一个文件夹Jvm放里面,去加载桌面文件夹下的class文件
Class<?> clazz = loader1.loadClass("Jvm.MyTest10");
System.out.println("class:"+clazz.hashCode());
Object object = clazz.newInstance();
System.out.println(object);
}
}
运行结果:
findClass invoked:Jvm.MyTest10 <--- 说明调用了自定义的类加载器loader1去加载MyTest10.class
class loader name:loader1 <---
class:1173230247
MyTest10 static block
Jvm.MyTest10@330bedb4
-
实例二:
-
前提:classpath中存在MyTest10.class文件,从最终的运行结果来看,创建了两个类加载器,第一个类加载器加载完MyTest.class之后,当第二次加载同样的类(MyTest.class)的时候,就不会加载,因为源码中应该有判断,如果该类已经被加载了,那么直接返回该对象即可。所以,对应返回的hashcode()相同,且MyTest10 static block只执行了一次。
-
但是,如果classpath中的MyTest10.class文件删除了,运行结果为下面所示,MyTest10类其实还是被加载了一次,【一个类只被加载一次定义并没有被打破,因为可以看到MyTest10类中的静态代码块MyTest10 static block只被执行了一次,说明MyTest10 只被加载一次】,但是可以看到,class输出的hashcode()不同,这里涉及到命名空间问题。
-
命名空间:
- 每一个类加载器都有自己的命名空间,命名空间由该类加载器及所有父类加载器所加载的类组成。
- 在同一个命名空间中,[ 不会出现类的完整名字(包括类的包名)相同的 ]两个类。
- 在不同的命名空间中,[ 有可能会出现类的完整名字(包括类的包名)相同的 ]两个类。
findClass invoked:Jvm.MyTest10 class loader name:loader1 class:1173230247 MyTest10 static block //第一加载,并初始化,静态代码块被执行力。 Jvm.MyTest10@330bedb4 findClass invoked:Jvm.MyTest10 class loader name:loader2 class:2125039532 Jvm.MyTest10@12a3a380
-
-
//这里只写了主函数
public static void main(String[] args) throws Exception {
MyTest16 loader1 = new MyTest16("loader1");
loader1.setPath("C:\\Users\\admin\\Desktop\\");
Class<?> clazz = loader1.loadClass("Jvm.MyTest10");
System.out.println("class:"+clazz.hashCode());
Object object = clazz.newInstance();
System.out.println(object);
System.out.println();
MyTest16 loader2 = new MyTest16("loader2");
loader1.setPath("C:\\Users\\admin\\Desktop\\");
Class<?> clazz1 = loader2.loadClass("Jvm.MyTest10");
System.out.println("class:"+clazz1.hashCode());
Object object1 = clazz.newInstance();
System.out.println(object1);
}
运行结果:
class:1836019240 <--
MyTest10 static block <--
Jvm.MyTest10@135fbaa4
class:1836019240 <--
Jvm.MyTest10@45ee12a7