前言
从上文我们分析了类加载器的过程与定义,我们从中得知,在JVM类加载器中有三大特性,其中缓存机制有效的保证了在同一个类加载器实例下,相同全名的类只加载一次,即loadClass方法不会被重复调用。我们这个小节就来看看它是如何利用缓存机制的已经它的流程是如何的。
源码定义
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
ClassLoader中的关于查询是否加载过类的方法也比较简单,在jdk1.8中使用的是直接内存,所以我们会用到直接内存进行缓存。这也就是我们的类变量为什么只会被初始化一次的由来。
缓存加载流程
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// 类加载的并发控制锁,本期不展开
synchronized (getClassLoadingLock(name)) {
// 在直接内存中查找是否已经加载过此类。
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 父加载器不为空,委托给父类加载器
c = parent.loadClass(name, false);
} else {
// 如果父加载器为空,代表当前是启动类加载器。
// 注意启动类加载器是C++实现的,在java中是获取不到的
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
// 它是当字节码加载到内存后进行链接操作,对文件格式和字节码验证,
// 并为static字段分配空间并初始化,符号引用转为直接引用,访问控制,方法覆盖等等
resolveClass(c);
}
return c;
}
}