1、首先将class文件放入指定本地目录下
2、编写自定义类加载器demo代码来加载class文件
/**
* @author WuSong
* @version 1.0
* @date 2022/12/7 12:07
* @description
*/
public class MyClassLoaderTest {
/**
* 1:继承ClassLoader类
* 2:重写findClass方法
*/
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为class对象,这个字节数组是class文件读取后最终的字节数组
return defineClass(name,data,0,data.length);
} catch (Exception e){
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String[] args) throws Exception {
// 初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
// java文件的本地目录:D:/test/com/wusong/jvm/User.java
Class clazz = classLoader.loadClass("com.wusong.jvm.User");
Object obj = (Object) clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj,null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
3、结果+类加载器机制测试
可以看到加载了User.class的类加载器为自定义类加载器:MyClassLoader,我们看下项目中的目录:
所以自定义类加载器的父类加载器为应用程序类加载器,因为所有类加载器的顺序都是先向上委托进行加载,如果父类加载器加载类失败,没有对应的类,才会向下委托到当前类加载器进行加载。
比如本次这个例子:
1、自定义加载器:MyClassLoader执行loadClass后:
会向上委托找应用程序类加载器进行该路径类的加载,应用程序类加载器又会向上委托找扩展类加载器进行加载,扩展类加载器又会向上委托到引导类加载器进行加载,然后引导类加载器发现没有该路径的类可供加载就会向下委托给扩展类加载器进行加载,扩展类加载器发现也没有该路径可加载的类就会向下委托给应用程序类加载器进行加载,应用程序类加载器发现也没有该类路径所对应的类可供加载,最后会向下委托给MyClassLoader自定义类加载器进行加载,然后MyClassLoader自定义类加载器发现本地目录有该类可供加载,就加载进来了。
所以加载了该类的加载器为:MyClassLoader
稍微调整一下:
将User类放入项目中的com.wusong.jvm目录中,然后我们运行一下类加载demo:
本次结果就为AppClassLoader:应用程序类加载器,因为在项目中有该类,就在应用程序类加载器这一步的时候加载成功了
4、打破双亲委派机制
想要打破双亲委派机制,在自定义类加载器当中重写loadClass方法即可,但是有些类是有沙箱安全机制的,jdk约定好了只能某个类加载器来进行加载,比如:java.lang.Object、java.lang.String等,打破双亲委派机制demo源码:
/**
* @author WuSong
* @version 1.0
* @date 2022/12/7 15:36
* @description
*/
public class MyClassLoaderTestTwo {
/**
* 1:继承ClassLoader类
* 2:重写findClass方法
* 3:打破双亲委派机制的核心就是重写loadClass方法
*/
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为class对象,这个字节数组是class文件读取后最终的字节数组
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
/**
* 打破双亲委派机制的核心就是重写loadClass方法
*/
@Override
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
// }
/**
* java.lang.Object 和 java.lang.Object 是java核心包,是有沙箱安全机制的,只能在最顶层的类加载器进行加载
* 在这里加上判断只有指定需要加载的类需要打破双亲委派机制,其它的类还是调用父类的类加载器进行加载即可
*/
if (!name.equals("com.wusong.jvm.User")) {
c = this.getParent().loadClass(name);
}
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
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
public static void main(String[] args) throws Exception {
// 初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
// java文件的本地目录:D:/test/com/wusong/jvm/User.java
Class clazz = classLoader.loadClass("com.wusong.jvm.User");
Object obj = (Object) clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
打破双亲委派机制就是:比如父类加载器可以加载user类,但是想在自定义加载器内加载user类,两个类的类路径一致,这时候就要打破双亲委派机制了,才可以加载自定义加载器内的user类,因为默认的机制的话是先向上委托父类加载器进行类的加载。
5、总结
沙箱机制:
在项目中创建了一个java.lang.String类,路径和jdk包里面的String类包路径一致,运行的时候就会报错,因为jdk的String类在引导类加载器的时候就已经加载了,而项目中的类是在应用程序类加载器中加载的,所以会报错,这就是Java类加载的沙箱机制,也避免了重复加载。
双亲委派机制:
加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。比如项目中的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。。双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载。
打破双亲委派机制:
想要打破双亲委派机制,在自定义类加载器当中重写loadClass方法即可,但是有些类是有沙箱安全机制的,jdk约定好了只能某个类加载器来进行加载,比如:java.lang.Object、java.lang.String等等,口说话说就是想要加载自己定义的一些类,但是和jdk底层的类路径冲突了,就需要打破双亲委派机制了,比如想重写和优化jdk底层的一些类,就写个自定义类加载器然后对其进行打破双亲委派机制进行类的加载即可。比如:想要优化java.lang.String类,在里面加个自定义方法,那么就把jdk里面的String类的所有内容复制到项目中自定义的String类中,然后加一个新方法,然后写一个自定义类加载器将项目中的String类进行加载即可(注意,类路径要一致,项目中的String类的路径也必须是java.lang.String)
注意:
同一个JVM内,两个相同包名和类名的类对象可以共存,因为他们的类加载器可以不一
样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都相同之外,还需要他们的类
加载器也是同一个才能认为他们是同一个。