jvm中默认定了三种classloader,分别为:bootstrap classloader, extension classloader, system classloader.
bootstrap 使用c语言来实现,没有对应的ClassLoader对象。
该方法String.class.getClassLoader() 返回null。
extension 用于从jre/lib/ext 目录加载“标准的扩展”。
system 用于加载应用类。由classpath环境变量中的 jar/zip 文件。
除此之外,java的classloader采用委托机制,即classloader都有一个 parent classloader。当收到一个类加载请求时,会先请求parent classloader加载类,如果parent classloader家在不到,再由自身尝试加载(如果加载不到,throw ClassNotFountException). 这种机制主要是出于安全考虑,如果用户自定义一个java.lang.Object 不至于覆盖jdk中的Object。
源码分析:
在代码中显式调用加载某个类:
xxx.class.getClassLoader().loadClass("xxx");
classLoader.loadClass(“xxx”)的执行过程:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
//find class
//findClass()的功能是找到class文件并把字节码加载到内存中。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
//加载字节码进入内存
//自定义的ClassLoader一般覆盖这个方法,以便使用不同的加载路径。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
//验证字节码
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
实现类的热部署
JVM默认不能热部署类,因为加载类时会去调用findLoadedClass(),如果类已被加载,就不会再次加载。
JVM判断类是否被加载有两个条件:完整类名是否一样、ClassLoader是否是同一个。
所以要实现热部署的话,只需要使用ClassLoader的不同实例来加载。
自定义ClassLoader
package classloader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class Main {
private static class MyClassLoader extends ClassLoader{
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class clazz = null;
try {
clazz = findLoadedClass(name);
//检查该类是否已经被装载。
if (clazz != null) {
return clazz;
}
byte[] bs = getClassBytes(name);
//从一个特定的信息源寻找并读取该类的字节。
if (bs != null && bs.length > 0) {
clazz = defineClass(name, bs, 0, bs.length);
}
if (clazz == null) {
//如果读取字节失败,则试图从JDK的系统API中寻找该类。
clazz = findSystemClass(name);
}
if (resolve && clazz != null) {
resolveClass(clazz);
}
} catch (IOException e) {
throw new ClassNotFoundException(e.toString());
}
System.out.println("class == " + clazz);
return clazz;
}
private byte[] getClassBytes(String className) throws IOException {
String path = System.getProperty("java.class.path") + File.separator;
path += className.replace('.', File.separatorChar) + ".class";
System.out.println(path);
FileInputStream fis = null;
try {
fis = new FileInputStream(path);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
//如果查找失败,则放弃查找。捕捉这个异常主要是为了过滤JDK的系统API。
}
byte[] bs = new byte[fis.available()];
fis.read(bs);
return bs;
}
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
System.out.println(System.getProperty("java.class.path"));
MyClassLoader cl1 = new MyClassLoader();
Class c1 = cl1.loadClass("Test");
c1.newInstance();
MyClassLoader cl2 = new MyClassLoader();
Class c2 = cl2.loadClass("Test");
c2.newInstance();
}
}
Tomcat的classLoader的作用(实现不同web app 的类隔离)
bootstrap / extension: 加载$JAVA_HOME/jre/lib/ext下的类
system: 加载由CLASSPATH初始化的所有类,对于tomcat自身类以及所有web应用的类可见。
Common: 对于tomcat,和所有web app 可见,用于加载
$CATALINA_BASE/conf/catalina.properties里面的类,通常应用程序的类不建议放在里面。WebappX: 加载所有WEB-INF/classes下面的类以及里面的jar。
该classloader有意违背了委托原则。它首先看WEB-INF/classes中是否有请求的类,而不是委托parent classloader去处理,但是jre 和servlet api 不会被覆盖。