- 启动类加载器
- 扩展类加载器
- 应用程序类加载器
- 自定义类加载器
- 双亲委派机制
1. 启动类加载器
Bootstrap ClassLoader,主要是负责加载Java安装目录下的核心类。Java安装目录下,就有一个“lib”目录,里面包含Java最核心的一些类库,一旦JVM启动,那么首先就会依托启动类加载器,去加载Java安装目录下的“lib”中的核心类库。
2.扩展类加载器
Extension ClassLoader,主要是负责加载Java安装目录下的扩展类。Java安装目录下,就有一个“lib\ext”目录,里面包含Java扩展类库,一旦JVM启动,那么首先就会依托扩展类加载器,去加载Java安装目录下的“lib\ext”中的类库。
3. 应用程序类加载器
Application ClassLoader,这类加载器就负责去加载“ClassPath”环境变量所指定的路径中的类。其实就是去加载你写好的Java代码,这个类加载器就负责加载你写好的那些类到内存里。
4.自定义类加载器
除了以上三种类加载器,JVM还支持用户自定义类加载器,用户可以根据需求编写类加载器去加载自己定义的类。通过继承 ClassLoader
,并覆盖 findClass
方法。
public class MyClassLoader extends ClassLoader {
public MyClassLoader(){
}
public MyClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File file = new File("D:/People.class");
try{
byte[] bytes = getClassBytes(file);
//defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}
private byte[] getClassBytes(File file) throws Exception {
// 这里要读入.class的字节,因此要使用字节流
FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel wbc = Channels.newChannel(baos);
ByteBuffer by = ByteBuffer.allocate(1024);
while (true){
int i = fc.read(by);
if(i == 0 || i == -1){
break;
}
by.flip();
wbc.write(by);
by.clear();
}
fis.close();
return baos.toByteArray();
}
}
5.双亲委派机制
JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器。
基于这个亲子层级结构,就有一个双亲委派的机制(先找父亲去加载,不行的话再由儿子来加载),假设你的应用程序类加载器需要加载一个类,他首先会委派给自己的父类加载器去加载,最终传导到顶层的类加载器去加载;但是如果父类加载器在自己负责加载的范围内,没找到这个类,那么就会下推加载权利给自己的子类加载器。
6.思考题
Tomcat的类加载机制是怎么设计?