首先明确下类加载的定义,类加载实际上就是将java class文件加载到java 虚拟机中,根据JVM规范的定义,一般分为2种类型的类加载,一种是启动类加载器,另外一种是用户自定义类加载器。
一 启动类加载器。
启动类加载器也有以下三种:Bootstrap ClassLoader、AppClassLoader和ExtClassLoader,这三种classloader在java虚拟机启动时会相继创建,首先启动时加载BootstrapClassLoader,然后BootstrapClassLoader加载ExtClassLoader,然后ExtClassLoader加载AppClassLoader,他们之间是由父子关系的,也就是通过父的classloader加载子classloader.下边分别介绍下这三种不同的classloader。
BootstrapClassLoader比较特殊,实际上它不是 java.lang.ClassLoader的子类,是C++编写的,java虚拟机启动时第一个执行,它是java虚拟机自带的装载器,用来装载核心类库,也就是java.lang.*,因为是c++编写的,所以在运行时,我们无法获取BootstrapClassLoader的任何信息。
ExtClassLoader的父亲是BootstrapClassLoader,但是在java运行时环境中,由于无法获取BootstrapClassLoader的任何信息,因此我们通过获取ExtClassLoader的parent的方式查看其父亲,会发现得到是null。ExtClassLoader的职责是负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。
AppClassLoader的父亲是ExtClassLoader,它的主要职责是加载用户应用系统所需要的类,如用户系统自己编写的class,或用户系统导入的其他jar架包。
下边用代码测试下这三种类加载器:
/**
* 测试类加载机制
* @author Administrator
*
*/
public class TestLoader
{
/**
* 分别加载三种不同类型的class类检验其用到的加载器。
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception
{
// 获取AppClassloader
ClassLoader appClassloader = ClassLoader.getSystemClassLoader();
// 获取ExtClassloader,也就是AppClassloader的父亲
ClassLoader extClassloader = ClassLoader.getSystemClassLoader().getParent();
// 获取BootStrapClassLoader
ClassLoader bootClassloader = ClassLoader.getSystemClassLoader().getParent().getParent();
// 下边分别打印输出
System.out.println("appClassloader:" + appClassloader);
System.out.println("extClassloader:" + extClassloader);
System.out.println("bootClassloader:" + bootClassloader);
// 根据三种加载器的职责分别通过加载具体的class来进一步测试下
// 加载核心class,也就是java.lang.*中的类
Class cls1 = Class.forName("java.lang.String");
// 加载扩展class,也就是/jre/lib/ext下的包
Class cls2 = Class.forName("sun.security.tools.KeyStoreUtil");
// 加载用户系统中的class,如这个类本身。
Class cls3 = Class.forName("TestLoader");
//获取三个class的类加载对象,然后打印输出。
ClassLoader cl1 = cls1.getClassLoader();
ClassLoader cl2 = cls2.getClassLoader();
ClassLoader cl3 = cls3.getClassLoader();
System.out.println("cl1:" + cl1);
System.out.println("cl2:" + cl2);
System.out.println("cl3:" + cl3);
}
}
运行测试类,输出如下结果
appClassloader:sun.misc.Launcher$AppClassLoader@19821f
extClassloader:sun.misc.Launcher$ExtClassLoader@addbf1
bootClassloader:null
cl1:null
cl2:null
cl3:sun.misc.Launcher$AppClassLoader@19821f
根据输出前面的三行的测试结果正确,跟我们预期的结果一致。可是cl2得输出结果不太明白,因为cl1加载的是核心类库,核心类库由BootstrapClassLoader加载,为null是正确的,但是cl2加载的是sun.security.tools.KeyStoreUtil类,而该类是/jre/lib/ext下的类,按理应该是由extClassloader加载,怎么输出的加载器也是null,这里不明白,怎么都搞不太清楚。如果有朋友研究过,可以一起探讨下,谢谢。
二 用户自定义类加载器。
有时,我们需要根据自身系统的需要,定义自己的类加载器。要定义自己的类加载,一般需要继承java.lang.ClassLoader类,然后重写loadClass或findClass方法即可。
下边写个自定义的测试类,来验证下,代码注释中已经说明很详细,不再详细说明,代码如下:
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
/**
* 自定义类加载器测试类
* @author Administrator
*
*/
public class MyClassLoader extends ClassLoader
{
// 加载的类文件所在的目录
private static final String DIR = "D://workspace//MapReduce//bin//";
/**
* 启动测试类的main方法
* @param args
* @throws Exception
*/
public static void main(String args[]) throws Exception
{
// 通过加载类,创建类的实例,调用类TestLoader的方法来测试是否成功装载指定的类
MyClassLoader clsLoader = new MyClassLoader();
// 装载class
Class cls = clsLoader.findClass("TestLoader");
// 创建其实例
Object instance = cls.newInstance();
// 获取test方法的Method对象,需要输入其参数类型,TestLoader类的test方法的传入参数是String类型,
// 所以这里是 java.lang.String.class
Method method = cls.getMethod("test", java.lang.String.class);
// 调用方法,传入参数为一字符串how are you
method.invoke(instance,"how are you");
}
/**
* 重写父类的findClass方法
*/
public Class findClass(String name) throws ClassNotFoundException
{
// 装载类
byte[] data = loadData(name);
// 调用父类的defineClass装载 类,并返回装载好的类的class实例
return defineClass(name, data, 0, data.length);
}
/**
* 装载类文件中的数据到内存,返回数据到一个字节数组中
* @param className 类名,不包括目录名和.class后缀。
* @return 类数据
* @throws Exception
* @throws ClassNotFoundException
*/
private byte[] loadData(String className)
{
String file = DIR + className + ".class";
FileChannel fileChannel = null;
try
{
// 这里主要是读文件里的数据到输出流,然后转换到一个字节数组中返回。
fileChannel = new FileInputStream(file).getChannel();
ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
WritableByteChannel writeChannel = Channels.newChannel(byteArrayStream);
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
while (true)
{
int index = fileChannel.read(byteBuffer);
if (index == 0 || index == -1)
{
break;
}
byteBuffer.flip();
writeChannel.write(byteBuffer);
byteBuffer.clear();
}
return byteArrayStream.toByteArray();
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
finally
{
try {
fileChannel.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
运行结果:
how are you
调用了加载类的test方法,打印出了传入的参数,完全正确。顺便附上test方法的定义:
public void test(String str)
{
System.out.println(str);
}
总结:理解java类加载机制,对掌握java的基础技术有重要的帮助,特别是对理解java的反射等特性,接下来有时间,再研究下java的反射特性,因为java的反射机制对编写工具软件是很有帮助的。