概述
这里要先说明下java中的ClassLoader 和Android中的ClassLoader是有区别的。
Android从5.0开始就采用art虚拟机, 该虚拟机有些类似Java虚拟机, 程序运行过程也需要通过ClassLoader 将目标类加载到内存中。本篇文章先带大家梳理下java的ClassLoader。
java中的ClassLoader
类加载就是多种类加载器(ClassLoader)来查找和加载Class文件到Java虚拟机中。类加载器就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。
ClassLoader的类型
java中的类加载器主要有两种类型,即系统类加载器和自定义类加载器。其中系统加载器包括3种。
- 启动类加载器(Bootstrap
ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。- 其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:
- 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
- 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
双亲委派模型
什么是双亲委派模型?
类加载器查找Class所采用的是双亲委派模式,如果一个类加载器收到类加载的请求,首先判断该类是否已经加载,如果没有不是自身去查找而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
为什么需要双亲委派模型,有什么好处?
双亲委派模型有两个好处
避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是直接读取已经加载的Class。
更加安全。
如果不使用双亲委派模型,就可以自定义一个 java.lang.String
类,这个类和系统的String类一样的功能,如果在这某个函数执行一些“恶意代码”。并且通过自定义类加载器加入到JVM
中。此时,如果没有双亲委派模型,那么JVM
就可能误以为自定义的java.lang.String
类是系统的String
类,导致“恶意代码”被执行。
而有了双亲委派模型,自定义的java.lang.String
类永远不会被加载进内存。因为首先是最顶端的类加载器加载系统的java.lang.String
类,即系统的String
类在java虚拟机启动时就被加载。或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String
类,不去通过调用父加载器不就好了吗。确实这样是可以操作的。但是在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型和比较的类型的类加载器不同,那么返回false。
所以java虚拟机判断两个对象是否是同一个类,需要两个类名一致并且被同一个类加载器加载。
如何实现双亲委派模型?
每次先通过父类加载器加载,当父类加载器无法加载时,再自己加载。
ClassLoader :: loadClass
protected Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
synchronized(this.getClassLoadingLock(var1)) {
Class var4 = this.findLoadedClass(var1); //首先检查传入的类是否已经加载,如果已经加载则不执行后续代码
if (var4 == null) {
long var5 = System.nanoTime();
try {
if (this.parent != null) {
var4 = this.parent.loadClass(var1, false); //如果父类加载器不为null,则调用父类加载器的 loadClass
} else {
var4 = this.findBootstrapClassOrNull(var1); //如果没有父类加载器,则执行自身 findBootstrapClassOrNull
}
} catch (ClassNotFoundException var10) {
}
if (var4 == null) { //继续向下进行查找流程
long var7 = System.nanoTime();
var4 = this.findClass(var1);
PerfCounter.getParentDelegationTime().addTime(var7 - var5);
PerfCounter.getFindClassTime().addElapsedTimeFrom(var7);
PerfCounter.getFindClasses().increment();
}
}
if (var2) {
this.resolveClass(var4);
}
return var4;
}
}
让我们最后小结一下 loadClass (String,boolean)
函数实现的双亲委派模型!
- 首先,检查一下置顶名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回
- 如果此类没有加载过,那么再判断一下是否有父加载器;如果有父加载器,则有父加载器(即调用
parent.loadClass(var1, false)
);或者调用Bootstrap
类加载器来加载- 如果父加载器以及
Bootstrap
类加载器都没有找到指定的类,那么调用当前类加载器的findClass
方法来完成类加载。
自定义ClassLoader
首先看下默认的classLoader
package main.java;
public class Test1 {
public void hello() {
System.out.println("当前类由" + getClass().getClassLoader().getClass()
+ " 加载");
}
}
输出
当前类由class sun.misc.Launcher$AppClassLoader 加载
注意
这里我们需要先将Test1.java 用 javac
编译成 Test1.class,再将Test.java
删除。因为如果Test.java
存放在当前目录中,根据双亲委派模型可知,会通过 sun.misc.Launcher$AppClassLoader
类加载器加载。
接下来我们自定义classLoader
package main.java;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(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;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
public static void main(String args[]) throws Exception {
File directory = new File("");//设定为当前文件夹
System.out.println(System.getProperty("java.version"));
try {
System.out.println(directory.getAbsolutePath());//获取绝对路径
} catch (Exception e) {
}
System.out.println("---->"+directory.getAbsolutePath()+"/src");
//要用自定义的classloader 需要将原先的Test1.java删除
CustomClassLoader customClassLoader = new CustomClassLoader(directory.getAbsolutePath()+"/src");//这里就是你当前存放class的路径
Class clazz = customClassLoader.loadClass("main.java.Test1");//这里就是你class文件的编译前的包名+类名
Object obj = clazz.newInstance();
Method helloMethod = clazz.getDeclaredMethod("hello", null);
helloMethod.invoke(obj, null);
}
}
输出
当前类由class main.java.CustomClassLoader 加载