准备动作:
public class MyCat {
public MyCat() {
System.out.println("MyCat is loader by:" + this.getClass().getClassLoader());
}
}
public class MySample {
public MySample() {
System.out.println("MySample is loader by:" + this.getClass().getClassLoader());
new MyCat();
}
}
package com.jvm.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class MyTest17 extends ClassLoader {
private String classLoaderName;
private String path; // class文件路径
private final String fileExtension = ".class";
public MyTest17(String classLoaderName) {
super(); // 将系统类加载器作为该类加载器的父类加载器
this.classLoaderName = classLoaderName;
}
public void setPath(String path) {
this.path = path;
}
public MyTest17(ClassLoader parent, String classLoaderName) {
super(parent); // 显式指定该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
@Override
public String toString() {
return "MyTest17{" +
"classLoaderName='" + classLoaderName + '\'' +
'}';
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
System.out.println("findClass:" + className);
System.out.println("class loader name:" + this.classLoaderName);
// 父类中findClass方法返回的是一个异常,所以必须要重写
byte[] data = this.loadClassData(className);
return this.defineClass(className, data, 0, data.length);
}
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));
System.out.println(this.path + className + this.fileExtension);
baos = new ByteArrayOutputStream();
int ch;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return data;
}
public class MyTest18 {
public static void main(String[] args) throws Exception {
MyTest17 loader1 = new MyTest17("loader1");
Class<?> aClass = loader1.loadClass("com.jvm.classloader.MySample");
System.out.println("aclass: " + aClass.hashCode());
Object o = aClass.newInstance();
}
}
我的想法:
MySample
应该是应用类加载器加载的,但是MySample
里面的MyCat
是哪个类加载器加载的呢?应该也是app吧。
实际结果:
aclass: 1163157884
MySample is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
MyCat is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
分析,通过反射的方式是对MySample
的主动使用,会初始化,当然也会被加载,而在MySample
的初始化方法中,new了一个MyCat
对象,当然也会被初始化和加载。
如果注视掉反射那行Object o = aClass.newInstance();
,则MyCat
不会被初始化,就不一定会被加载,可以通过jvm参数来观察是否被加载。
此时的核心问题是:加载MyCat
的类加载器跟加载MySample
的是不是同一个类加载器?
操作:在桌面上保存这个这两个类的class文件,main方法如下:
public static void main(String[] args) throws Exception {
MyTest17 loader1 = new MyTest17("loader1");
loader1.setPath("C:/Users/lifeline张/Desktop/");
Class<?> aClass = loader1.loadClass("com.jvm.classloader.MySample");
System.out.println("aclass: " + aClass.hashCode());
Object o = aClass.newInstance();
}
1、删除ide中的mycat的class文件,运行代码。此时会报class文件找不到异常。两者都是由app加载的。运行结果如下:
aclass: 1163157884
MySample is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread "main" java.lang.NoClassDefFoundError: com/jvm/classloader/MyCat
at com.jvm.classloader.MySample.<init>(MySample.java:6)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at com.jvm.classloader.MyTest18.main(MyTest18.java:9)
Caused by: java.lang.ClassNotFoundException: com.jvm.classloader.MyCat
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 7 more
2、如果删除MySample
的class文件,保留mycat的,此时正常输出,sample由loader1加载,cat由app加载。
findClass:com.jvm.classloader.MySample
class loader name:loader1
C:/Users/lifeline张/Desktop/com/jvm/classloader/MySample.class
aclass: 356573597
MySample is loader by:MyTest17{classLoaderName='loader1'}
MyCat is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
3、在2的基础上,改变MyCat
的实例化方法:
public class MyCat {
public MyCat() {
System.out.println("MyCat is loader by:" + this.getClass().getClassLoader());
System.out.println(MySample.class);
}
}
输出结果为:
findClass:com.jvm.classloader.MySample
class loader name:loader1
C:/Users/lifeline张/Desktop/com/jvm/classloader/MySample.class
aclass: 356573597
MySample is loader by:MyTest17{classLoaderName='loader1'}
MyCat is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread "main" java.lang.NoClassDefFoundError: com/jvm/classloader/MySample
at com.jvm.classloader.MyCat.<init>(MyCat.java:6)
at com.jvm.classloader.MySample.<init>(MySample.java:6)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at java.lang.Class.newInstance(Class.java:442)
at com.jvm.classloader.MyTest18.main(MyTest18.java:9)
Caused by: java.lang.ClassNotFoundException: com.jvm.classloader.MySample
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 8 more
加载是都加载完了,但是在mycat中没有找到mysqmple的Class
对象。本质原因是两个类是由不同的类加载器加载,属于不同的命名空间
4、改造mycat与myxample如下:
public class MyCat {
public MyCat() {
System.out.println("MyCat is loader by:" + this.getClass().getClassLoader());
// System.out.println(MySample.class);
}
}
public class MySample {
public MySample() {
System.out.println("MySample is loader by:" + this.getClass().getClassLoader());
new MyCat();
System.out.println("from MySample" + MyCat.class);
}
}
将两者class文件放到桌面上,删除sample的class文件,运行结果如下:
findClass:com.jvm.classloader.MySample
class loader name:loader1
C:/Users/lifeline张/Desktop/com/jvm/classloader/MySample.class
aclass: 356573597
MySample is loader by:MyTest17{classLoaderName='loader1'}
MyCat is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
from MySampleclass com.jvm.classloader.MyCat
这个跟3形成了对比,自定义类加载器命名空间里面包含了其父类的命名空间中的类。
总结:**子加载器所加载的类可以访问父加载器加载的类;而父加载器加载的类无法访问子加载器所加载的类。**核心原因就是:命名空间由该加载器及所有父加载器所加载的类组成