ClassLoader一个常常出现又让不少人望而却步的词,本文将试图以最浅显易懂的方式来说解 ClassLoader,但愿能对不了解该机制的朋友起到一点点做用。
要深刻了解ClassLoader,首先就要知道ClassLoader是用来干什么的,顾名思义,它就是用来加载Class文件到JVM,以供程序使用的。咱们知道,java程序能够动态加载类定义,而这个动态加载的机制就是经过ClassLoader来实现的,因此可想而知ClassLoader的重要性如何。
看到这里,可能有的朋友会想到一个问题,那就是既然ClassLoader是用来加载类到JVM中的,那么ClassLoader又是如何被加载呢?难道它不是java的类?
没有错,在这里确实有一个ClassLoader不是用java语言所编写的,而是JVM实现的一部分,这个ClassLoader就是bootstrap classloader(启动类加载器),这个ClassLoader在JVM运行的时候加载java核心的API以知足java程序最基本的需求,其中就包括用户定义的ClassLoader,这里所谓的用户定义是指经过java程序实现的ClassLoader,一个是ExtClassLoader,这个ClassLoader是用来加载java的扩展API的,也就是/lib/ext中的类,一个是AppClassLoader,这个ClassLoader是用来加载用户机器上CLASSPATH设置目录中的Class的,一般在没有指定ClassLoader的状况下,程序员自定义的类就由该ClassLoader进行加载。
当运行一个程序的时候,JVM启动,运行bootstrap classloader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),而后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。
上面大概讲解了一下ClassLoader的做用以及一个最基本的加载流程,接下来将讲解一下ClassLoader加载的方式,这里就不得不讲一下ClassLoader在这里使用了双亲委托模式进行类加载。
每个自定义ClassLoader都必须继承ClassLoader这个抽象类,而每一个ClassLoader都会有一个parent ClassLoader,咱们能够看一下ClassLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前ClassLoader的parent,注意,这个parent不是指的被继承的类,而是在实例化该ClassLoader时指定的一个ClassLoader,若是这个parent为null,那么就默认该ClassLoader的parent是bootstrap classloader,这个parent有什么用呢?
咱们能够考虑这样一种状况,假设咱们自定义了一个ClientDefClassLoader,咱们使用这个自定义的ClassLoader加载java.lang.String,那么这里String是否会被这个ClassLoader加载呢?事实上java.lang.String这个类并非被这个ClientDefClassLoader加载,而是由bootstrap classloader进行加载,为何会这样?实际上这就是双亲委托模式的缘由,由于在任何一个自定义ClassLoader加载一个类以前,它都会先委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader没法加载成功后,才会由本身加载,在上面这个例子里,由于java.lang.String是属于java核心API的一个类,因此当使用ClientDefClassLoader加载它的时候,该ClassLoader会先委托它的父亲ClassLoader进行加载,上面讲过,当ClassLoader的parent为null时,ClassLoader的parent就是bootstrap classloader,因此在ClassLoader的最顶层就是bootstrap classloader,所以最终委托到bootstrap classloader的时候,bootstrap classloader就会返回String的Class。
咱们来看一下ClassLoader中的一段源代码:
java
protectedsynchronized Class loadClass(String name, boolean resolve) 程序员
throws ClassNotFoundException bootstrap
{ api
// 首先检查该name指定的class是否有被加载缓存
Class c = findLoadedClass(name); 安全
if (c == null) { 网络
try { app
if (parent != null) { jvm
//若是parent不为null,则调用parent的loadClass进行加载ide
= parent.loadClass(name, false);
} else {
//parent为null,则调用BootstrapClassLoader进行加载
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
//若是仍然没法加载成功,则调用自身的findClass进行加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
经过ClassLoader咱们能够自定义类加载器,定制本身所须要的加载方式,例如从网络加载,从其余格式的文件加载等等均可以,其实ClassLoader还有不少地方没有讲到,例如ClassLoader内部的一些实现等等,原本但愿可以讲得简单易懂一点,但是结果本身看回头好像感受并不怎么样,郁闷,看来本身的文笔仍是差太多了,但愿可以给一些有须要的朋友一点帮助吧。
JVM在加载类的时候,都是经过ClassLoader的loadClass()方法来加载class的,loadClass(String name)方法:
使用的是双亲委托模式:
jvm启动时,会启动jre/rt.jar里的类加载器:bootstrap classloader,用来加载java核心api;而后启动扩展类加载器ExtClassLoader加载扩展类,并加载用户程序加载器AppClassLoader,并指定ExtClassLoader为他的父类;
当类被加载时,会先检查在内存中是否已经被加载,若是是,则再也不加载,若是没有,再由AppClassLoader来加载,先从jar包里找,没有再从classpath里找;
若是自定义loader类,就会存在这命名空间的状况,不一样的加载器加载同一个类时,产生的实例实际上是不一样的;
Java代码
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
loadClass(String name)方法再调用loadClass(String name, boolean resolve)方法:
◆ name - 类的二进制名称
◆ resolve - 若是该参数为 true,则分析这个类
Java代码
protectedsynchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
//JVM 规范规定ClassLoader能够在缓存保留它所加载的Class,若是一个Class已经被加载过,则直接从缓存中获取
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
protectedsynchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
//JVM 规范规定ClassLoader能够在缓存保留它所加载的Class,若是一个Class已经被加载过,则直接从缓存中获取
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
若是ClassLoader并无加载这个class,则调用findBootstrapClass0:
Java代码
private Class findBootstrapClass0(String name)
throws ClassNotFoundException
{
check();
if (!checkName(name))
thrownew ClassNotFoundException(name);
return findBootstrapClass(name);
}
private Class findBootstrapClass0(String name)
throws ClassNotFoundException
{
check();
if (!checkName(name))
thrownew ClassNotFoundException(name);
return findBootstrapClass(name);
}
该方法会调用check()方法来判断这个类是否已经初始化,而且经过checkName(name)来判断由name指定的这个类是否存在最后调用findBootstrapClass(name):
Java代码
privatenative Class findBootstrapClass(String name)
throws ClassNotFoundException;
privatenative Class findBootstrapClass(String name)
throws ClassNotFoundException;
而这个findBootstrapClass方法是一个native方法,这是咱们的root loader,这个载入方法并不是是由JAVA所写,而是C++写的,它会最终调用JVM中的原生findBootstrapClass方法来完成类的加载。
若是上面两个都找不到,则使用findClass(name)来查找指定类名的Class:
Java代码
protected Class<?> findClass(String name) throws ClassNotFoundException {
thrownew ClassNotFoundException(name);
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
thrownew ClassNotFoundException(name);
}
JDK5.0中的说明:
使用指定的二进制名称查找类。此方法应该被类加载器的实现重写,该实现按照委托模型来加载类。在经过父类加载器检查所请求的类后,此方法将被 loadClass 方法调用。默认实现抛出一个ClassNotFoundException。
因此,咱们在自定义类中,只须要重写findClass()便可。
MyClassLoader类:
Java代码
publicclass MyClassLoader extends ClassLoader {
private String fileName;
public MyClassLoader(String fileName) {
this.fileName = fileName;
}
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class clazz = this.findLoadedClass(className);
if (null == clazz) {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new
ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
byte[] bytes = baos.toByteArray();
clazz = defineClass(className, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return clazz;
}
privatebyte[] loadClassBytes(String className) throws
ClassNotFoundException {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new
ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
} catch (IOException fnfe) {
thrownew ClassNotFoundException(className);
}
}
private String getClassFile(String name) {
StringBuffer sb = new StringBuffer(fileName);
name = name.replace('.', File.separatorChar) + ".class";
sb.append(File.separator + name);
return sb.toString();
}
}
publicclass MyClassLoader extends ClassLoader {
private String fileName;
public MyClassLoader(String fileName) {
this.fileName = fileName;
}
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class clazz = this.findLoadedClass(className);
if (null == clazz) {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new
ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
byte[] bytes = baos.toByteArray();
clazz = defineClass(className, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return clazz;
}
privatebyte[] loadClassBytes(String className) throws
ClassNotFoundException {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new
ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
} catch (IOException fnfe) {
thrownew ClassNotFoundException(className);
}
}
private String getClassFile(String name) {
StringBuffer sb = new StringBuffer(fileName);
name = name.replace('.', File.separatorChar) + ".class";
sb.append(File.separator + name);
return sb.toString();
}
}
该类中经过调用defineClass(String name, byte[] b, int off, int len)方法来定义一个类:
Java代码
protectedfinal Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
protectedfinal Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
注:MyClassLoader加载类时有一个局限,必需指定.class文件,而不能指定.jar文件。该类中的大部分代码是从网上搜索到的,是出自一牛人之笔,只是不知道原帖在哪,但愿不会被隐藏。
MainClassLoader类:
Java代码
publicclass MainClassLoader {
publicstaticvoid main(String[] args) {
try {
MyClassLoader tc = new MyClassLoader("F:\\OpenLib\\");
Class c = tc.findClass("Test");
c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
publicclass MainClassLoader {
publicstaticvoid main(String[] args) {
try {
MyClassLoader tc = new MyClassLoader("F:\\OpenLib\\");
Class c = tc.findClass("Test");
c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
最后是一个简单的Test测试类:
Java代码
publicclass Test
{
public Test() {
System.out.println("Test");
}
publicstaticvoid main(String[] args) {
System.out.println("Hello World");
}
}