一、原理:使用不同类加载器,加载的同名类文件,在JVM中并是不同的Class对象
同一个类加载器,对于同一个类名(如:com.xxx.Test),只能加载一次
当输入:a时,类加载器为:
新实例化出来的HotSwapClassLoader加载器的父加载器==》HotSwapClassLoader.class.getClassLoader()
此处为:App class loader
当输入:b或者c时,类加载器为:
新实例化出来的HotSwapClassLoader加载器本身,加载方法为:
defineClass(null, classByte, 0,classByte.length)
defineClass(null, classByte, 0,classByte.length)
重点:每个类加载器对象,自身有一个已加载Class对象池
子加载器,委托父加载器,加载的,二者池中均有此类对象
父加载器自己加载的类对象,子加载器,可以自己另行再加载一个“同名”类对象
图 1. 类加载器树状组织结构示意图
二、类加载器的代理模式
类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
比如一个 Java 类 com.example.Sample
,编译之后生成了字节代码文件 Sample.class
。两个不同的类加载器ClassLoaderA
和 ClassLoaderB
分别读取了这个 Sample.class
文件,并定义出两个 java.lang.Class
类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException
。下面通过示例来具体说明。
三、示例共有三个类
1、主类
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
public class Test {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
HotSwapClassLoader classloader = new HotSwapClassLoader();
while (true) {
String str = br.readLine();
if (str.equals("0")) {
break;
} else if (str.equals("a")) {
} else {
classloader = new HotSwapClassLoader();
/**
* 以下注释为,本次测试重点信息
*/
// 下面一行:类加载器在尝试自己去查找某个类的字节代码并定义它时,
// 会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推
// 也就是说:如存在此行时,下一行,就报错
// java.lang.LinkageError: loader (instance of
// HotSwapClassLoader): attempted duplicate class definition for
// name: "MainTest"
// Class clazz = Class.forName("MainTest", true, classloader);
classloader.loadByPath("C:/Users/michael/Desktop/file/temp/" + str + "/MainTest.class");
}
Class clazz = Class.forName("MainTest", true, classloader);
Method method = clazz.getMethod("main", new Class[] { String[].class });
method.invoke(null, new String[] { null });
}
}
}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
public class Test {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
HotSwapClassLoader classloader = new HotSwapClassLoader();
while (true) {
String str = br.readLine();
if (str.equals("0")) {
break;
} else if (str.equals("a")) {
} else {
classloader = new HotSwapClassLoader();
/**
* 以下注释为,本次测试重点信息
*/
// 下面一行:类加载器在尝试自己去查找某个类的字节代码并定义它时,
// 会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推
// 也就是说:如存在此行时,下一行,就报错
// java.lang.LinkageError: loader (instance of
// HotSwapClassLoader): attempted duplicate class definition for
// name: "MainTest"
// Class clazz = Class.forName("MainTest", true, classloader);
classloader.loadByPath("C:/Users/michael/Desktop/file/temp/" + str + "/MainTest.class");
}
Class clazz = Class.forName("MainTest", true, classloader);
Method method = clazz.getMethod("main", new Class[] { String[].class });
method.invoke(null, new String[] { null });
}
}
}
2、类加载器
import java.io.FileInputStream;
import java.io.InputStream;
public class HotSwapClassLoader extends ClassLoader {
public HotSwapClassLoader() {
super(HotSwapClassLoader.class.getClassLoader());
System.out.println(HotSwapClassLoader.class.getClassLoader());
}
public Class loadByte(byte[] classByte) {
return defineClass(null, classByte, 0, classByte.length);
}
public Class loadByPath(String filePath) throws Exception {
InputStream is = new FileInputStream(filePath);
byte[] b = new byte[is.available()];
is.read(b);
is.close();
return defineClass(null, b, 0, b.length);
}
}
import java.io.FileInputStream;
import java.io.InputStream;
public class HotSwapClassLoader extends ClassLoader {
public HotSwapClassLoader() {
super(HotSwapClassLoader.class.getClassLoader());
System.out.println(HotSwapClassLoader.class.getClassLoader());
}
public Class loadByte(byte[] classByte) {
return defineClass(null, classByte, 0, classByte.length);
}
public Class loadByPath(String filePath) throws Exception {
InputStream is = new FileInputStream(filePath);
byte[] b = new byte[is.available()];
is.read(b);
is.close();
return defineClass(null, b, 0, b.length);
}
}
3、需要热替换的测试类
public class MainTest {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println("AAAAAAAAAAAAAAAAAAA");
}
}
public class MainTest {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println("AAAAAAAAAAAAAAAAAAA");
}
}