加载器
类加载是通过类加载器完成的。
有两种类型的类加载器
-
Java虚拟机自带的加载器
- 根类加载器(Bootstrap)。
- 扩展类加载器(Extension)。
- 系统(应用)类加载器(System)。
-
用户自定义的类加载器
- java.lang.ClassLoader的子类。
- 用户可以定制类的加载方式。
注:上图不是继承关系而是包含关系。
- Bootstrap ClassLoader : 启动(根)类加载器,可以加载
$JAVA_HOME中jre/lib/rt.jar
里所有的class,由C++实现,不是ClassLoader子类,它没有父加载器, - Extension ClassLoader : 扩展类加载器负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中
jre/lib/*.jar
或-Djava.ext.dirs
指定目录下的jar包。 - App ClassLoader :引用(系统)类加载器负责加载classpath中
指定的jar包及目录中class
。 - 若有一个类加载器能够成功加载Test类(自己定义的类),那么这个类加载器被称为定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器)都被称为初始类加载器, 一般继承自
ClassLoader
类。
类加载器的双亲委托机制
- 在双亲委托机制中,各个加载器按照父子关系形成了树形结构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器。
- 当一个类加载器想要加载某个类时,它自身不会立刻加载,而是会委托它的双亲(父亲)类加载器去加载。
来个样例,来证明不同的类加载器是加载特定目录下的class
- 在开始之前先来阅读一番jdk源码。
- 可知在一些情况下用
null
表示Bootstrap ClassLoader
。
public class MyClassLoader{
public static void main(String[] args) throws ClassNotFoundException {
Class<?> stringClazz = Class.forName("java.lang.String");
Class<?> myClazz = Class.forName("com.jvmstudy.classloading.MyClassLoader");
System.out.println(stringClazz.getClassLoader());
System.out.println("==========================");
System.out.println(myClazz.getClassLoader());
}
}
- 结果
- 自定义的类就由应用(App)类加载器加载。
类的加载
- 类加载器并不需要等到某个类被“首次主动使用”时再加载它。
- JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)。
- 如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
获取类的方式
- 通过加载器获得类:
loader.loaderClass(className)
- 通过反射获得类:
Class.forName(className)
写一些代码
验证各加载器的关系
- 代码
public class ClassLoaderTest {
public static void main(String[] args) {
//获取系统类加载器,即应用(App)类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println(classLoader.toString());
while (classLoader!=null){
classLoader = classLoader.getParent();//获取类加载器的父类加载器
System.out.println(classLoader);
}
}
}
- 结果
获取类的绝对路径
- 代码
public class ClassLoaderTest2 {
public static void main(String[] args) throws IOException {
//返回此线程的上下文类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String resourceName = "com/jvmstudy/classloading/ClassLoaderTest2.class";//类所在的相对路径
Enumeration<URL> enumeration = classLoader.getResources(resourceName);//可以获取到类的绝对路径
while (enumeration.hasMoreElements()){
URL url = enumeration.nextElement();
System.out.println(url);
}
}
}
- 结果
获得ClassLoader的途径
- 获得当前类的ClassLoader
clazz.getClassLoader();
//clazz是类,
- 获得当前线程上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
- 获得系统的ClassLoader
ClassLoader.getSystemClassLoader();
- 获得调用者的ClassLoader
DriverManager.getCallerClassLoader();
小结
- 就一点,类是类对象是对象。
- 一定要将类和对象区分开来。