java中的class在使用之前,需要通过classloader加载到jvm以后才可以使用。
具体来讲,.java文件被编译成中间字节码文件.class,classloader读取.class文件,转换成java.lang.Class类的实例,之后才可以new各种的对象。
默认情况下,jvm有三级class loader:
BootStrap ClassLoader
Extension ClassLoader
App ClassLoader
采用双亲委派机制进行加载,某个特定的class loader在接收到类的加载请求时,首先将加载任务委托给父类加载器,如果父类已经加载过该class,则直接返回加载的class对象;如果没有则继续往上寻找父类加载器,如果在最顶层都没有加载该class,则自上而下尝试加载该类,任何一级加载到该类则返回,最后未加载到则跑出class not found异常;
加载流程相关的代码可以参考:java.lang.ClassLoader#loadClass(java.lang.String, boolean)
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;
}
}
之所以叫“双亲”委派机制,主要是App ClassLoader之上有两个parent class loader,猜想。。
其中的一些理解:
(1)jvm中,只有当你使用一个class的时候,才会触发class loader的加载(有一些特殊情况,即使没有使用也会加载)
(2)不同的class loader有不同的命名空间。也就是说,不同的class loader去加载同一个class文件,jvm层面会认为是不同的类。
(3)双亲委派机制在很大程度上避免了内存中出现多个相同的字节码文件
(4)BootStrap ClassLoader的parent是null,Extension ClassLoader的parent是BootStrap ClassLoader,App ClassLoader的parent是Extension Classloader;
对于刚才提到的三级class loader,
BootStrap Classload由C++编写,是jvm层面的类加载器,负载加载jvm的核心基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等,"rt"表示runtime,运行时。由于是用C++代码实现的,且涉及到JVM的一些本地化的实现,所有我们无法获取到该classloader的引用。
命令行添加-verbose:class可以看到jvm启动过程中load的class列表,如:
BootStrap classloader的加载目录由系统属性:sun.boot.class.path 指定
System.out.println(System.getProperty("sun.boot.class.path"));
Extension Classloader采用java编写,主要是加载 %JRE_HOME/lib/ext 目录下的jar和class,具体的加载目录由系统属性:java.ext.dirs指定
App ClassLoader负责加载当前java应用中的classpath中的类,具体的加载目录由系统属性:java.class.path决定
Bootstrap ClassLoader由JVM启动,然后初始化sun.misc.Launcher ,sun.misc.Launcher初始化Extension ClassLoader、App ClassLoader。
举个例子看一下
public class MainApplication {
public static void main(String[] args) {
System.out.println(Void.class.getClassLoader());
System.out.println(MainApplication.class.getClassLoader());
}
}
运行的结果如下:
null
sun.misc.Launcher$AppClassLoader@18b4aac2
这里Void类是通过BootStrap ClassLoader加载的,由于其是用C++写的,这里返回NULL
MainApplication 是通过App ClassLoader加载的,可以看到打印出来了。
其中类名Launcher$AppClassLoader采用$符号进行了分隔,$之后的是内部类,说明AppClassLoader是Launcher的一个内部类,翻看相关代码,可以看到,AppClassLoader和ExtClassLoader都是Launcher的内部类,且都继承自URLClassLoader。