Java类加载器的做用是寻找类文件,而后加载Class字节码到JVM内存中,连接(验证、准备、解析)并初始化,最终造成能够被虚拟机直接使用的Java类型。java
类加载器种类
有两种类加载器:bootstrap
1. 启动类加载器(Bootstrap ClassLoader)安全
由C++语言实现(针对HotSpot VM),负责将存放在%JAVA_HOME%\jre\lib目录或-Xbootclasspath参数指定的路径中的类库加载到JVM内存中,像java.lang.、java.util.、java.io.*等等。能够经过vm参数“-XX:+TraceClassLoading”来获取类加载信息。咱们没法直接使用该类加载器。eclipse
2. 其余类加载器(Java语言实现)ide
1)扩展类加载器(Extension ClassLoader)测试
负责加载%JAVA_HOME%\jre\lib\ext目录或java.ext.dirs系统变量指定的路径中的全部类库。咱们能够直接使用这个类加载器。this
2)应用程序类加载器(Application ClassLoader),或者叫系统类加载器spa
负责加载用户类路径(classpath)上的指定类库,咱们能够直接使用这个类加载器。通常状况,若是咱们没有自定义类加载器默认就是用这个加载器。.net
3)自定义类加载器code
经过继承ClassLoader类实现,主要重写findClass方法,这个地方有个坑,在eclipse中重写了findClass方法,发现该方法并无执行,解决办法就是把loadClass方法也重写了,而后在该方法中优先使用重写的findClass方法查找目标类,若是查询不到再使用父类的loadClass方法查询.
类加载器使用顺序
在JVM虚拟机中,若是一个类加载器收到类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器完成。每一个类加载器都是如此,只有当父加载器在本身的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试本身去加载。
也就是说,对于每一个类加载器,只有父类(依次递归)找不到时,才本身加载 。这就是双亲委派模型。
为何须要双亲委派模型呢?
这能够提升Java的安全性,以及防止程序混乱。
1)提升安全性方面:
假设咱们使用一个第三方Jar包,该Jar包中自定义了一个String类,它的功能和系统String类的功能相同,可是加入了恶意代码。那么,JVM会加载这个自定义的String类,从而在咱们全部用到String类的地方都会执行该恶意代码。
若是有双亲委派模型,自定义的String类是不会被加载的,由于最顶层的类加载器会首先加载系统的java.lang.String类,而不会加载自定义的String类,防止了恶意代码的注入。
2)防止程序混乱
假设用户编写了一个java.lang.String的同名类,若是每一个类加载器都本身加载的话,那么会出现多个String类,致使混乱。若是本加载器加载了,父加载器则不加载,那么以哪一个加载的为准又不能肯定了,也增长了复杂度。
JVM眼中的相同的类
在JVM中,不可能存在一个类被加载两次的事情!一个类若是已经被加载了,当再次试图加载这个类时,类加载器会先去查找这个类是否已经被加载过了,若是已经被加载过了,就不会再去加载了。
可是,若是一个类使用不一样的类加载器去加载是能够出现屡次加载的状况的!也就是说,在JVM眼中,相同的类须要有相同的class文件,以及相同的类加载器。当一个class文件,被不一样的类加载器加载了,JVM会认识这是两个不一样的类,这会在JVM中出现两个相同的Class对象!甚至会出现类型转换异常!
自定义类加载器
咱们能够自定义类加载器,只需继承ClassLoader抽象类,并重写findClass方法(若是要打破双亲委派模型,须要重写loadClass方法)。缘由能够查看ClassLoader的源码:
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;
}
}
这个是ClassLoader中的loadClass方法,大体流程以下:
1)检查类是否已加载,若是是则不用再从新加载了;
2)若是未加载,则经过父类加载(依次递归)或者启动类加载器(bootstrap)加载;
3)若是还未找到,则调用本加载器的findClass方法;
以上可知,类加载器先经过父类加载,父类未找到时,才有本加载器加载。
由于自定义类加载器是继承ClassLoader,而咱们再看findClass方法:
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
能够看出,它直接返回ClassNotFoundException。
所以,自定义类加载器必须重写findClass方法。
自定义类加载器示例代码:
package blog.csdn.net;
import java.io.FileInputStream;
/**
* 自定义的类加载器,能够加载指定目录下的class文件
*
* @author mChenys
*
*/
class MyClassLoader extends ClassLoader {
//被加载的class文件存放目录
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
// 先去加载器里面看看已经加载到的类中是否有这个类
Class> c = findLoadedClass(name);
if (c == null) {
//先用本类加载器查询
try {
c = this.findClass(name);
} catch (Exception e) {
}
if(c==null) {
//本类加载不到再用父类的方法进行双亲委派机制查
c =super.loadClass(name);
}
}
return c;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
throw new ClassNotFoundException();
}
}
/**
* 获取加载的class文件的字节流数据
*
* @param name
* @return
* @throws Exception
*/
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;
}
}
Person类:
package blog.csdn.net;
public class Person {
public Person() {
System.out.println("Person本类加载器:" + getClass().getClassLoader());
System.out.println("Person父类加载器:" + getClass().getClassLoader().getParent());
}
public String print() {
System.out.println("Person:print()执行了...");
return "PersonPrint";
}
}
测试类:
package blog.csdn.net;
import java.lang.reflect.Method;
public class _Main {
public static void main(String[] args) throws Exception {
// 建立自定义加载器,经过构造方法传入class文件所在根目录
MyClassLoader myClassLoader = new MyClassLoader("E:/Workspaces/eclipse/classloader/bin");
// 开始加载类,注意:Person.class文件必须放在E:/Workspaces/eclipse/classloader/bin/目录下
Class> clazz = myClassLoader.loadClass("blog.csdn.net.Person");
// 下面是反射的操做...
Object o = clazz.newInstance();
Method print = clazz.getDeclaredMethod("print");
String result = (String) print.invoke(o);
System.out.println("print方法执行结果:" + result);
}
}
执行结果:
经过结果能够验证Person.class文件是经过自定义的类加载器加载的.