一、类加载器的作用
将class文件字节码内容加载到内存中,并将这些静态的数据转换成方法去中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为这个方法去中类数据的访问入口。
二、类缓存
标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM的垃圾回收器(GC)会回收这些class对象。
三、类加载器的层次结构
1、引导类加载器(bootstrap class loader)
1)、用来加载Java的核心库,是用原生代码(C/C++)来实现的,并不继承来自java.lang.ClassLoader类;
2)、加载扩展类和应用程序类加载器并指定他们的父类加载器。
2、扩展类加载器 (extensions class loader)
1)、用来加载Java的扩展库,Java虚拟机的实现提供了一个扩展目录。该类加载器在此目录里面查找并加载Java类。
2)、由sun.misc.Launcher$ExtClassLoader实现
3、应用程序类加载器
1)、根据Java应用的类路径(classpath,java.class.path路类)一般来说java应用的类都是由它来完成加载的。
2)、由sun.misc.LauncherLoader$AppClassLoader实现
4、自定义类加载器
可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
四、类加载器的代理模式
1、代理模式
交给其他加载类了加载指定的类
2、双亲委托机制
1)、某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯。如果父类加载器可以完成类加载的任务,就成功返回,只有父类加载器无法完成此加载任务的时候,在自己加载。
2)、双亲委托机制是为了保证Java核心库的类型安全,保证不会出现用户自己定义java.lang.Object类的情况。
3)、类加载器除了用于加载类,也是安全的最基本的屏障。
3、双亲委托机制是代理模式的一种
1)、并不是所有的类加载器都采用双亲委托机制
2)、Tomcat服务器类加载器也使用代理模式,所不同的是它首先尝试去加载这个类,如果找不到在委托给其父类加载,与一般的类加载器的顺序相反。
五、自定义类加载器
1、继承java.lang.ClassLoader类
2、首先检查请求的类型是否已经被这个类加载器加载到命名空间了,如果已加载了则直接返回,不用重新加载了。
3、委派类加载,请求父类加载,如果父类加载器能够完成,则返回父类加载器加载的class实例
4、调用本类加载器的findClass()方法,试图获取对应的字节码,如果获取的到,则调用defineClass()导入类型到方法区;如果获取不到或出现错误,则返回异常给loadClass(),抛出异常,终止加载。
package com.silence.loader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定义文件系统类加载器
* @author zhuxiang
*
*/
public class FileSystemClassLoader extends ClassLoader{
private String rootDir;
public FileSystemClassLoader(String rootDir){
this.rootDir = rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clz = findLoadedClass(name);
//判断该类是否已经加载,如果没有则加载,反之不加载,直接返回
if (clz != null) {
return clz;
}else {
ClassLoader parent = this.getParent();
try {
clz = parent.loadClass(name);//委派给父类加载
} catch (Exception e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}
if (clz != null) {
return clz;
}else {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}else {
clz = defineClass(null, classData, 0,classData.length);
}
}
}
return clz;
}
private byte[] getClassData(String className){
String path = rootDir + "/" + className.replace(".", "/") + ".class";
//IOUtils可以使用它将流中的数据转换成字节数组
InputStream iStream = null;
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
try {
iStream = new FileInputStream(path);
byte[] buffer = new byte[1024];
int temp = 0;
while ((temp = iStream.read(buffer))!=-1) {
bStream.write(buffer, 0, temp);
}
return bStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}finally{
try {
iStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
注意:被两个类加载的同一个类,JVM不认为是相同的类,同一个类加载器加载同一个类,JVM才认为是相同的类。