类加载器概述
类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class实例。一旦一个类被加入JVM中,同一个类就不会被再次加入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。
例如以下案例,在JVM中两个同名的Person类是完全不同的,之间也互不兼容,因为类加载器不同。
上述情况转换成代码如下:
- 定义一个Person类。
public class Person {
private Person instance;
public void setPerson(Object instance) {
this.instance = (Person) instance;
}
}
- 编写一个测试方法,用于测试通过两个不同类加载器加载Person类得到不同的Class实例。
@Test
public void ClassIdentityTest() throws Exception{
String classDataRootPath = "WebRoot\\WEB-INF\\classes";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
String className = "app.java.classloader.Person";
try {
Class<?> class1 = fscl1.findClass(className);
Object obj1 = class1.newInstance();
Class<?> class2 = fscl2.findClass(className);
Object obj2 = class2.newInstance();
Method setSampleMethod = class1.getMethod("setPerson", java.lang.Object.class);
setSampleMethod.invoke(obj1, obj2);
} catch (Exception e) {
e.printStackTrace();
}
}
- 上述代码运行后,报如下错误,表示虽然两个对象的类名称相同,但是由于通过不同类加载器得到Class实例,JVM不会认为是相同的。
类加载器分类
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构。
- Bootstrap ClassLoader:根类加载器。
- Extension ClassLoader:扩展类加载器。
- System ClassLoader:系统类加载器。
Bootstrap ClassLoader被称为引导(或原始或根)类加载器,它负责加载Java的核心类。在JVM中,当执行java.exe命令时,使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。根类加载器非常特殊,它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。
@Test
public void BootstrapTest(){
// 获取根类加载器所加载的全部URL数组
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
// 遍历输出根类加载器加载的全部URL
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
}
Extension ClassLoader被称为扩展类加载器,它负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext或由java.ext.dirs系统属性指定的目录)中的JAR包的类。通过这种方式可以为Java扩展核心类以外的新功能,只要把自己开发的类打包成JAR文件,然后放入%JAVA_HOME%/jre/lib/ext路径即可。
@Test
public void ExtensionTest() throws Exception{
// 位于jre/lib/ext/dnsns.jar
DNSNameService dnsNameService = new DNSNameService();
System.out.println(dnsNameService.getClass().getClassLoader());
}
System ClassLoader被称为系统或应用类加载器,它负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类径路。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以系统类加载器作为父加载器。
@Test
public void SystemTest() throws Exception{
// 获取当前类的实例对象
ClassLoaderTest classLoaderTest = new ClassLoaderTest();
System.out.println(classLoaderTest.getClass().getClassLoader());
}
以下是JVM中4种类加载器的层次结构:
@Test
public void ClassLoaderTreeTest() throws Exception{
// 获取当前类的类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
// 判断类加载器是否为空
while (classLoader != null) {
// 打印当前类加载器的名称
System.out.print(classLoader.getClass().getName()+"->");
// 获取当前类加载器的父级
classLoader = classLoader.getParent();
}
System.out.println(classLoader);
}
上述代码中可以看出JVM中类加载器的层次结构。最后输出的是null的愿意是因为根类加载器并不是Java提供的,而是JVM提供的,所以不能获取。
类加载机制
JVM的类加载器机制主要有如下三种:
- 全盘负责。所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
- 父类委托。所谓父类委托,则是先让父类加载器视图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
- 缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。这也是为什么修改Class后,必须重新启动JVM,程序所做的修改才会生效的原因。
自定义类加载器
JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,可以通过扩展ClassLoader的子类,并重写该ClassLoader提供的方法来实现自定义的类加载器。
ClassLoader类具有如下两个关键方法:
- loadClass(String name, boolean resolve):该方法为ClassLoader的入口点,根据指定名称来加载类,系统就是调用ClassLoader的该方法来获取指定类对应的Class对象。
- findClass(String name):根据指定名称来查找类。
由于loadClass()方法的执行步骤为1)利用findLoadedClass()方法来检查是否已经加载类。2)在父类加载器调用loadClass()方法。3)调用findClass()方法查找类。重写findClass()方法可以避免默认类加载器的父类委托和缓冲机制,这里推荐重写findClass()方法。
下面来实现一个文件系统类加载器,用于加载存储在文件系统上的Java字节代码。
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace(‘.‘, File.separatorChar) + ".class";
}
}
转载说明:请注明作者及原文链接,谢谢!