类加载器的作用
- 通过一个类的全限定名称来获取此类的二进制字节流,并加载到内存中(需要使用类加载器)
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在堆中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
类缓存
标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过,JVM垃圾收集器可以回收这些Class对象
类加载器的层级结构(树状结构)
1.引导类加载器(Bootstrap ClassLoader)
它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.Path路径下的内容),是用原生代码来实现的,并不继承自java.lang.classloader。
加载扩展类和应用程序类加载器,并指定他们的父类加载器。
启动类加载器无法被Java程序直接引用
2.扩展类加载器(Extension ClassLoader)
- 用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jar或java.ext.dirs路径下的内容)。 Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类。
- 由sun.misc.Launcher$ExtClassLoader实现。
**3.应用程序类加载器(Application ClassLoader) **
- 它根据Java应用的类路径(classpath,java.class.path类。 一般来说,Java应用的类都是由它来完成加载的。
- 由sun.misc.Launcher$AppClassLoader实现。
4.自定义类加载器
- 开发人员可以用过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的要求
双亲委派模式
双亲委派模型
从JDK1.2开始,java虚拟机规范推荐开发者使用双亲委派模式(ParentsDelegation Model)进行类加载,其加载过程如下:
- 如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器去完成。
- 每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。
- 如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出ClassNotFoundException,而不再调用其子类加载器去进行类加载。
双亲委派 模式的类加载机制的优点是java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。
注意:
- 并不是所有的类记载其都采用双亲委托机制
- tomcat服务器类加载器也是用代理模式,所不同的是它首先尝试去加载某个类,如果找不到再找代理给父类加载器。这与一般类加载器的顺序是相反的。
我们可以简单地自定义一个类加载器,用于加载某个class
public class FileSystemClassLoader extends ClassLoader {
//文件的根目录
private String rootDir;
public FileSystemClassLoader(String rootDir){
this.rootDir=rootDir;
}
//重写findClass方法
@Override
protected Class<?> findClass(String s) throws ClassNotFoundException {
Class c=findLoadedClass(s);
if (c!=null){
return c;
}else {
ClassLoader parent=this.getParent();
//parent获取不到class时会抛出异常,为了继续执行使用try catch包裹
try{
c=parent.loadClass(s);
}catch (Exception e){
}
if (c!=null){
return c;
}else {
byte[] classData=getClassData(s);
if (classData==null){
throw new ClassNotFoundException();
}else {
//将字节数组转为Class
c=defineClass(s,classData,0,classData.length);
}
}
}
return c;
}
//将文件转为字节数组
private byte[] getClassData(String className) {
//改为文件地址
String path=rootDir+"/"+className.replace(".","/")+".class";
System.out.println(path);
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
InputStream inputStream=null;
try {
inputStream=new FileInputStream(path);
byte[] buffer=new byte[1024];
int temp=0;
while ((temp=inputStream.read(buffer))!=-1){
byteArrayOutputStream.write(buffer,0,temp);
}
return byteArrayOutputStream.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
if (inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (byteArrayOutputStream!=null){
try {
byteArrayOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
使用
public class UseCustomClassLoader {
public static void main(String[]args){
FileSystemClassLoader loader=new FileSystemClassLoader("/home/xjk");
FileSystemClassLoader loader2=new FileSystemClassLoader("/home/xjk");
try {
Class clazz1=loader.findClass("com.jk.bean.Emp");//本项目自定义的类调用AppClassLoader
System.out.println(clazz1.getClassLoader());
Class clazz2=loader.findClass("java.lang.String");//rt.jar里的类调用BootstrapClassLoader
System.out.println(clazz2.getClassLoader());
Class clazz3=loader.findClass("com.company.Main");//项目外的类调用自定义的FileSystemClassLoader
System.out.println(clazz3.getClassLoader());
Class clazz4=loader2.findClass("com.company.Main");//使用不同类加载器,Class对象不一致
System.out.println(clazz4.getClassLoader());
System.out.println(clazz3==clazz4);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果
sun.misc.Launcher$AppClassLoader@18b4aac2
null
com.jk.jvm.FileSystemClassLoader@1d44bcfa
com.jk.jvm.FileSystemClassLoader@6f94fa3e
false
因为BootstrapClassLoader无法被Java程序直接引用,所以显示为空