类加载器
-
jvm自带的加载器
-
根类加载器(BootStrap)
该加载器没有父加载器,它负责加载虚拟机的核心类库,如java.lang.*等.跟类加载器从系统属性sun.boot.class.path所指定的目录中加载类库.根类加载器的实现依赖于底层操作系统,属于虚拟机的实现部分,由C++实现,并没有继承java.lang.ClassLoader
-
扩展类加载器(Extension)
它的父加载器为根类加载器,它从java.ex.dirs系统属性所指定的目录中加载类库,或者从JDK安装目录的jre\lib\ext子目录下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载.扩展类加载器是纯java类,是java.lang.ClassLoader的子类
-
系统(应用)类加载器(System)
它的父加载器为拓展加载器,它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类库,它是用户自定义的类加载的默认父类.系统类加载器是纯java类,是java.lang.ClassLoader的子类
-
-
用户自定义的类加载器
- java.lang.ClassLoader的子类
- 用户可以定制类的加载方式
JVM规范允许类加载器在预料某个类将要被使用时就于预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LingageError)
如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化
类加载器时用来把类加载到虚拟机中,从JDK1.2版本开始,类的加载过程采用双亲委派机制,这种机制能更好的保证java平台的安全.在此委托机制中,除了java虚拟机自带的跟类加载器以外,其余的类加载器都有且只有一个父加载器,当java程序请求加载器loader1加载Sample类时,loader1首先委托自己的父加载器去加载Sample类,若父加载器能加在,则由父加载器完成加载任务,否则才有加载器loader1本身来加载Sample类
获取ClassLoader的途径:
- 获取当前类的:clazz.getClassLoader();
- 获取当前线程上下文的ClassLoader:Thread.currentThread().getContextClassLoader()
- 获取系统的ClassLoader:ClassLoader.getSystemClassLoader()
- 获取调用者的ClassLoader:DriverManager.getCallerClassLoader()
类加载器的命名空间:
每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父类加载器所加载的类组成
在同一个命名空间中,不会出现类的全限定名完全相同的两个类
在不同的命名空间中,有可能会出现类的全限定名完全相同的两个类
public class MyTest16 extends ClassLoader {
private String classLoaderName;
private final String fileExtension = ".class";
//加载类的路径
private String path;
public void setPath(String path) {
this.path = path;
}
/**
* @param classLoaderName 类加载器的名字
* <p>
* * Creates a new class loader using the <tt>ClassLoader</tt> returned by
* * the method {@link #getSystemClassLoader()
* * <tt>getSystemClassLoader()</tt>} as the parent class loader.
*/
public MyTest16(String classLoaderName) {
//将系统类加载器当作该类加载器的父类加载器
super();
this.classLoaderName = classLoaderName;
}
public MyTest16(ClassLoader parentClassLoader, String classLoaderName) {
//显示指定该类加载器的父类加载器
super(parentClassLoader);
this.classLoaderName = classLoaderName;
}
/**
* 根据二进制的类的名字查找类,并返回其Class对象
*
* @param className
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
System.out.println("findClass invoked");
byte[] data = this.loadClassData(className);
//将字节数组转化为类的Class对象
return this.defineClass(className, data, 0, data.length);
}
/**
* 根据类的名字返回类数据的字节数组
*
* @param clasaName
* @return
*/
private byte[] loadClassData(String clasaName) {
System.out.println("loadClassData invoked");
InputStream in = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
clasaName = clasaName.replace(".", "\\");
try {
in = new FileInputStream(new File(this.path + clasaName + this.fileExtension));
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = in.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return data;
}
@Override
public String toString() {
return "[ " + this.classLoaderName + " ]";
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
MyTest16 loader1 = new MyTest16("loader1");
//loader1.setPath("D:\\mingbyte\\jvm_study\\target\\classes");
loader1.setPath("C:\\Users\\10025\\Desktop\\");
Class<?> aClass = loader1.loadClass("com.jvm.class_loader.MyTest1");
//根据类的Class对象生成一个实例
Object o = aClass.newInstance();
System.out.println("o 的类加载器: "+o.getClass().getClassLoader());
System.out.println();
MyTest16 loader2 =new MyTest16("loader2");
loader2.setPath("C:\\Users\\10025\\Desktop\\");
Class<?> clazz = loader2.loadClass("com.jvm.class_loader.MyTest1");
Object o2 = clazz.newInstance();
System.out.println("o2 的类加载器: "+o2.getClass().getClassLoader());
}
}
输出结果:
findClass invoked
loadClassData invoked
o 的类加载器: [ loader1 ]
findClass invoked
loadClassData invoked
o2 的类加载器: [ loader2 ]
结果分析:
- 当项目中存在MyTest1.class文件时,会由系统类加载器进行加载
- 删除项目中的MyTest1.class文件,在桌面com\jvm\class_loader目录下增加MyTest1.class文件,会由我们自定义的类加载器进行加载,并且不同的类加载器都可以加载该文件,因为不同类加载器的命名空间不同