类加载器负责加载所有的类,系统为所有被载入内存中的类生成一个java.lang.Class实例。一旦一个类被加入JVM中,同一个类就不会被再次加入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。
例如以下案例,在JVM中两个同名的Person类是完全不同的,之间也互不兼容,因为类加载器不同。
上述情况转换成代码如下:
public class Person {
private Person instance;
public void setPerson(Object instance) {
this.instance = (Person) instance;
}
}
@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();
}
}
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构。
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的类加载器机制主要有如下三种:
JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,可以通过扩展ClassLoader的子类,并重写该ClassLoader提供的方法来实现自定义的类加载器。
ClassLoader类具有如下两个关键方法:
由于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";
}
}
转载说明:请注明作者及原文链接,谢谢!
原文地址:http://blog.csdn.net/longestory/article/details/46317737