什么是双亲委派机制
- 一个类在加载时,类加载器会先判断是否已经加载过这个类,如果没有,就往上向父类加载器传递,调用父类加载器的的loadClass方法来加载。当父类加载器无法完成加载任务的时候,子类再加载该类。这样做的用意在哪里呢?
- 比如现在有一个应用类,在双亲委派机制下,第一次加载时,先传递到Bootstrap,发现Bootstrap无法加载后,往下传递到Extension中加载。发现Extension无法加载后,往下传递到Application中加载。在第二次加载时,发现已经在Application中加载过了,就直接由Application加载。如果没有双亲委派机制,那么每次都需要从上而下尝试加载一遍。
- 双亲委派机制下,第一次,Application->Extension->Bootstrap->Extension->Application;第二次,Application。
- 不使用双亲委派机制,第一次,Bootstrap->Extension->Application;第二次Bootstrap->Extension->Application。
实现代码
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
自定义类加载器
-
从代码中看到,双亲委派下,类的加载过程代码实现: 1、findLoadedClass来判断是否加载过 2、如果没有加载过,判断父类加载器是否为空,若为空,用bootstrap的findBootstrapClassOrNull来加载。 3、如果以上步骤依旧没有加载成功if(c==null)。那就使用当前类的findClass方法去加载。
-
在自定义类加载器时,一般都是继承ClassLoader,重写findClass()方法
-
如果需要打破双亲委派,则需要重写loadClass
static class MyClassLoader extends ClassLoader{
private String classpath;
public MyClassLoader(String classpath){
this.classpath = classpath;
}
//com.entity.user->com/entity/user.class
private byte[] getByte(String name) throws Exception {
name = name.replaceAll("\.","/");
FileInputStream fileInputStream = new FileInputStream(classpath + "/" + name + ".class");
int len = fileInputStream.available();
byte[] data = new byte[len];
fileInputStream.read(data);
fileInputStream.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = getByte(name);
return defineClass(name,data,0,data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
@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();
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//// 如果不是load开头的,交由父加载,是的话,自己加载
if(!name.startsWith("load")){
c = this.getParent().loadClass(name);
}else{
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;
}
}
}
- 准备三个测试文件
//项目中
public class Game {
private String name;
...
public void play(){
System.out.println("pokemon");
}
}
//test1中
public class Game {
private String name;
...
public void play(){
System.out.println("Mario");
}
}
//test2中
public class Game {
private String name;
...
public void play(){
System.out.println("Zelda");
}
}
- 测试代码:
public static void main(String[] args) throws Exception {
Class clazz1 = Game.class;
System.out.println(clazz1.getClassLoader().getClass().getName());
Object object1 = clazz1.newInstance();
Method method1 = clazz1.getMethod("play",null);
method1.invoke(object1,null);
MyClassLoader myClassLoader = new MyClassLoader("/Users/d/Desktop/byfile/test1");
Class clazz2 = myClassLoader.loadClass("load.Game");
System.out.println(clazz2.getClassLoader().getClass().getName());
Object object2 = clazz2.newInstance();
Method method2 = clazz2.getMethod("play",null);
method2.invoke(object2,null);
MyClassLoader myClassLoader1 = new MyClassLoader("/Users/d/Desktop/byfile/test2");
Class clazz3 = myClassLoader1.loadClass("load.Game");
System.out.println(clazz3.getClassLoader());
Object object3 = clazz3.newInstance();
Method method3 = clazz3.getMethod("play",null);
method3.invoke(object3,null);
}
- 测试结果:
- 结果分析: 可以看到Game在被AppClassLoader加载过后,在之后加载时,依旧使用了自定义类加载器。 可能有同学会有疑问,自己写的类默认继承的Object也会被自定义类加载器加载吗?看loadClass中的这段代码:
if(!name.startsWith("load")){
c = this.getParent().loadClass(name);
}else{
c = findClass(name);
}
由于自定义加载器无法加载Object类,在打破双亲委派机制时,需要选择性的跳过,只有自己写的类才被自定义类加载器加载,其他的依旧需要jvm默认的加载器来加载。当把上面这段代码去掉运行后,程序会报错: java.io.FileNotFoundException: /Users/d/Desktop/byfile/test1/java/lang/Object.class (No such file or directory)