JAVA启动后,是经过JVM各级ClassLoader来加载各个类到内存。为了更加了解加载过程,我通过分析和写了一个简单的ClassLoader来粗浅的分析它的原理。
JVM的ClassLoader分三层,分别为Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader,他们不是类继承的父子关系,是逻辑上的上下级关系。
Bootstrap ClassLoader是启动类加载器,它是用C++编写的,从%jre%/lib目录中加载类,或者运行时用-Xbootclasspath指定目录来加载。
Extension ClassLoader是扩展类加载器,从%jre%/lib/ext目录加载类,或者运行时用-Djava.ext.dirs制定目录来加载。
System ClassLoader,系统类加载器,它会从系统环境变量配置的classpath来查找路径,环境变量里的.表示当前目录,是通过运行时-classpath或-Djava.class.path指定的目录来加载类。
一般自定义的Class Loader可以从java.lang.ClassLoader继承,不同classloader加载相同的类,他们在内存也不是相等的,即它们不能互相转换,会直接抛异常。java.lang.ClassLoader的核心加载方法是loadClass方法,如:
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
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;
}
通过上面加载过程,我们能知道JVM默认是双亲委托加载机制,即首先判断缓存是否有已加载的类,如果缓存没有,但存在父加载器,则让父加载器加载,如果不存在父加载器,则让Bootstrap ClassLoader去加载,如果父类加载失败,则调用本地的findClass方法去加载。
双亲委托机制的作用是防止系统jar包被本地替换,因为查找方法过程都是从最底层开始查找。 因此,一般我们自定义的classloader都需要采用这种机制,我们只需要继承java.lang.ClassLoader实现findclass即可,如果需要更多控制,自定义的classloader就需要重写loadClass方法了,比如tomcat的加载过程,这个比较复杂,可以通过其他文档资料查看相关介绍。
各个ClassLoader加载相同的类后,他们是不互等的,这个当涉及多个ClassLoader,并且有通过当前线程上线文获取ClassLoader后转换特别需要注意,可以通过线程的setContextClassLoader设置一个ClassLoader线程上下文,然后再通过Thread.currentThread().getContextClassLoader()获取当前线程保存的Classloader。但是自定义的类文件,放到Bootstrap ClassLoader加载目录,是不会被Bootstrap ClassLoader加载的,因为作为启动类加载器,它不会加载自己不熟悉的jar包的,并且类文件必须打包成jar包放到加载器加载的根目录,才可能被扩展类加载器所加载。
Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader三者的关系如下:
Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent。
但是这并不是继承关系,只是语义上的定义,基本上,每一个ClassLoader实现,都有一个Parent ClassLoader。
可以通过ClassLoader的getParent方法得到当前ClassLoader的parent。Bootstrap ClassLoader比较特殊,因为它不是java class所以Extension ClassLoader的getParent方法返回的是NULL。
由于一些特殊的需求,我们可能需要定制ClassLoader的加载行为,这时候就需要自定义ClassLoader了.
自定义ClassLoader需要继承ClassLoader抽象类,重写findClass方法,这个方法定义了ClassLoader查找class的方式。
主要可以扩展的方法有:
findClass 定义查找Class的方式
defineClass 将类文件字节码加载为jvm中的class
findResource 定义查找资源的方式
现已有的ClassLoader实现有如下几种:
java.net.URLClassLoader
java.security.SecureClassLoader
java.rmi.server.RMIClassLoader
sun.applet.AppletClassLoader
Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子类。
这个是URLClassLoader的构造方法:
public URLClassLoader(URL[] urls, ClassLoader parent)
public URLClassLoader(URL[] urls)
urls参数是需要加载的ClassPath url数组,可以指定parent ClassLoader,不指定的话默认以当前调用类的ClassLoader为parent。
由于classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入。
所以,当我们自定义的classloader加载成功了com.company.MyClass以后,MyClass里所有依赖的class都由这个classLoader来加载完成。
Class.forName() 与 load.loadClass()的区别:
Class.forName("xx.xx")等同于Class.forName("xx.xx",true,CALLClass.class.getClassLoader()),第二个参数(bool)表示装载类的时候是否初始化该类,即调用类的静态块的语句及初始化静态成员变量。
ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也可以用(ClassLoader.getSystemClassLoader())
Class cls = loader.loadClass("xx.xx"); //这句话没有执行初始化,其实与Class.forName("xx.xx",false,loader)是一致的,只是loader.loadClass("xx.xx")执行的是更底层的操作。
只有执行cls.NewInstance()才能够初始化类,得到该类的一个实例
Class的装载分了三个阶段,loading,linking和initializing,分别定义在The Java Language Specification的12.2,12.3和12.4。
Class.forName(className) 实际上是调用Class.forName(className, true, this.getClass().getClassLoader())。注意第二个参数,是指Class被loading后是不是必须被初始化。
ClassLoader.loadClass(className)实际上调用的是ClassLoader.loadClass(name, false),第二个参数指出Class是否被link。
区别就出来了。Class.forName(className)装载的class已经被初始化,而ClassLoader.loadClass(className)装载的class还没有被link。
forName支持数组类型,loadClass不支持数组
一般情况下,这两个方法效果一样,都能装载Class。但如果程序依赖于Class是否被初始化,就必须用Class.forName(name)了。
例如,在JDBC编程中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver"),如果换成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。
为什么呢?打开com.mysql.jdbc.Driver的源代码看看,
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
原来,Driver在static块中会注册自己到java.sql.DriverManager。而static块就是在Class的初始化中被执行。所以这个地方就只能用Class.forName(className)。