1、java.lang.ClassLoader
功能:用于加载类,根据类名加载类字节码(.class文件),定义一个Class类实例;
常用方法:
01、getParent():获取当前加载器的父类加载器;
02、loadClass(String name):通过指定的类名,加载类字节码,生成一个java.lang.Class的实例;
03、findClass(String name):通过指定的类名,查找对应的Class实例;
04、defineClass(String name, byte[] b, int off, int len):将字节数组转换为Java类,返回Class的实例,这个方式是final的,不可被继承;
2、Java类加载器的类型
01、引导类加载器(Bootstrap Class Loader)
最高层的类加载器,主要用来加载核心类库:jre/lib/rt.jar,它是原生c++编写的,没有继承自java.lang.ClassLoader,在java中无法获取其对象;
02、扩展类加载器(Extensions Class Loader)
中间层的类加载器,它是加载java的扩展库:jre/ext/*.jar,java虚拟机提供了一个扩展库目录,该类加载器会加载该目录的java类;
03、应用类加载器(App Class Loader)
最底层的类加载器,它负责加载CLASSPATH下的类,我们编写的应用程序,都是由该加载器加载的,可以通过this.getClass().getClassLoader()查看名字,类似这样:sun.misc.Launcher$AppClassLoader@15db9742
三种加载器举例 :
public class SnailTest {
@Test
public void testClassLoader() {
// 应用——类加载器
System.out.println(ClassLoader.getSystemClassLoader());
// 扩展——类加载器
System.out.println(ClassLoader.getSystemClassLoader().getParent());
// 引导——类加载器
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}
}
输出:
sun.misc.Launcher$AppClassLoader@5cad8086
sun.misc.Launcher$ExtClassLoader@49476842
null
可以看出ClassLoader类是由AppClassLoader加载,它的父亲是ExtClassLoader,ExtClassLoader的父亲无法获取是因为它是用C++实现。
3、自定义类加载器
除了系统提供的三种加载器外,我们还可以自己定义类加载器,实现方法是:
01、编写自定义类,继承java.lang.ClassLoader
02、自己实现一个加载类字节码的方法,比如:将.class文件读取到字节数组,然后再通过父类的defineClass方法构造一个Class实例;
自定义实现:
public class CustomClassLoader extends ClassLoader {
private String rootPath;
public CustomClassLoader(String rootPath) {
this.rootPath = rootPath;
}
private byte[] getFileByte(String name) throws IOException {
File file = new File(rootPath + name + ".class");
return FileUtils.readFileToByteArray(file);
}
protected Class<?> getClass(String name) throws Exception {
byte[] bytes = getFileByte(name);
return super.defineClass(name, bytes, 0, bytes.length);
}
public static void main(String[] args) {
CustomClassLoader ccl = new CustomClassLoader("C:\\jvm\\");
try {
Class clazz = ccl.getClass("Test");
Object obj=clazz.newInstance();//这个对象的构造方法会输出construct
} catch (Exception e) {
e.printStackTrace();
}
}
}
4、双亲委派机制
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托交给父类加载器,父类加载器又将加载任务向上委托,直到最父类加载器,如果最父类加载器可以完成类加载任务,就成功返回,如果不行就向下传递委托任务,由其子类加载器进行加载。
双亲委派机制的好处:
01、保证java核心库的安全性(例如:如果用户自己写了一个java.lang.String类就会因为双亲委派机制不能被加载,不会破坏原生的String类的加载)
02、避免类重复加载
代理机制:与双亲委派机制相反,代理机制是先自己尝试加载,如果无法加载则向上传递。tomcat就是代理模式。
5、类加载过程
01、装载:读取字节码文件.class文件;
02、链接:
验证:确保加载的.class文件合法,符合jvm规范,没有安全问题;
准备:为类的静态变量分配内存;
解析:把虚拟机常量池中的符号引用转换为直接引用;
03、初始化:为类的静态变量赋予正确的初始值。
说明:解析,Java 中,虚拟机会为每个加载的类维护一个常量池【不同于字符串常量池,这个常量池只是该类的字面值(例如类名、方法名)和符号引用的有序集合。 而字符串常量池,是整个JVM共享的】这些符号(如int a = 5;中的a)就是符号引用,而解析过程就是把它转换成指向堆中的对象地址的相对地址。
6、类的初始化步骤:
01、如果这个类还没有被加载和链接,那先进行加载和链接
02、假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
03、如果类中存在static标识的块,那就依次执行这些初始化语句。