1.类加载核心类,关于类加载详见:Java类的加载。
2.ClassLoader作用:将类二进制数据加载为JVM中的Class对象
3.关于阅读ClassLoader源码之前,最好理解一些相关体系、结构及相关名词含义:JVM安全体系:双亲委派、命名空间、策略、保护域。
源码解析:
public abstract class ClassLoader {//ClassLoader是个抽象类
private static native void registerNatives();
static {
registerNatives();//本地方法注册
}
private final ClassLoader parent;//父加载器
/**
* 封装了并行的可装载的类型的集合(static)
* 维护一张表,里面指出哪些类加载器具有并发能力,注意区分于parallelLockMap
*/
private static class ParallelLoaders {
private ParallelLoaders() {}
// 具有并发能力的ClassLoader集合表(static)
private static final Set> loaderTypes =
Collections.newSetFromMap(
new WeakHashMap, Boolean>());
static {
synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class);//默认ClassLoader具有并发能力,很显然如此
}
}
/**
* 若其所有父类是具有并发能力的,则其也是
*/
static boolean register(Class extends ClassLoader> c) {
synchronized (loaderTypes) {
if (loaderTypes.contains(c.getSuperclass())) {
//检查父类是否是即可,是,加入,不是则返回false
loaderTypes.add(c);
return true;
} else {
return false;
}
}
}
/**
* 返回给定的ClassLoader是否具有并发能力
*/
static boolean isRegistered(Class extends ClassLoader> c) {
synchronized (loaderTypes) {
return loaderTypes.contains(c);
}
}
}
//具有并发能力的类加载器:锁对象map(并发时候加锁对象)
//String是该加载器加载的类的className,Object是其锁对象
//parallelLockMap是一些时候导致内存溢出的原因:大量的类被创建,不止增加Class对象,同样增加该map大小
private final ConcurrentHashMap parallelLockMap;
// 类加载器:包签名(包权限资格)列表
private final Map package2certs;
private static final Certificate[] nocerts = new Certificate[0];
//该加载器加载的类的集合
private final Vector> classes = new Vector<>();
//默认的保护域
private final ProtectionDomain defaultDomain =
new ProtectionDomain(new CodeSource(null, (Certificate[]) null),
null, this, null);
//被该类加载加载的类的保护域
private final Set domains;
// 加载后,将该类Class对象加载到集合中
void addClass(Class c) {
classes.addElement(c);
}
//该加载器可加载的包
private final HashMap packages = new HashMap<>();
//若存在安全管理器,检查是否可创建该加载器
//security.checkCreateClassLoader()若不可以,直接抛异常
private static Void checkCreateClassLoader() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
return null;
}
//私有构造器,若具有安全管理器,且不允许创建该类加载器,则直接抛异常
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
//若该类加载器具有并行能力,初始化相关数据,均为线性安全集合
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet());
assertionLock = new Object();
} else {
//若该类加载器具有并行能力,初始化相关数据,均为非线性安全集合
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
//可指定父类构造器
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
//构造器,默认当前系统加载器为父加载器
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
// -- Class --
//加载指定名称的类,详见loadClass(name, false)
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
/**
* 加载指定二进制数据(流)成Class对象
* 加载顺序:
*
* 首先,findLoadedClass(String),检查该类是否已被加载,已被加载不再加载
*
* 其次,尝试使用父加载器(有父加载器一直向上,即为双亲委派)加载该类
*
* 最后,若加载不成功,尝试自身加载该类
*
* 如果,参数resolve=true,进行类解析,详见resolveClass()
*
* 这个方法是可以重写的,但不推荐,重写会破坏双亲委派,推荐重写findClass方法
*/
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//注意下面,如果该类加载器支持并发加载,则可以并发地加载各类,否则只能同步加载不同类(这一步同样可以防止并发地加载出同一个类的不同Class对象)
//详见下方getClassLoadingLock(name)
synchronized (getClassLoadingLock(name)) {
/**
* 最先,会查找该类是否已经被加载过
* 这里的查找是native方法实现,其实现逻辑是同一个全名称类+同
* 一个加载器方为同一个类,方为该类已加载
*/
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异常由最末加载器抛出
}
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) {
resolveClass(c);
}
return c;//记住,这里是自下而上(加载器链)试图加载的,上面不行,接着
//由下面加载器加载,直到最末
}
}
//获取加载类时候,对应类的并发锁对象
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
//若该加载器支持并发,则不同类具有不同的锁对象,若不存则新建,返回对象的锁对象
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
//若不支持并发,则直接返回该加载器作为锁对象,即该加载器只能同步加载任何类
return lock;
}
分章总结:
1.JVM存在一张表loaderTypes,记录每一个类加载器是否具有并发加载能力
2.每一个类加载器,如果父类支持并发加载,其默认也具有
3.不具有并发能力,意思是这个加载器同时只能加载一个类;而具有的话,对于不同的类,这个加载器可以同时加载,当然同一个类是不能并发加载的,除非你重写loadClass(String name, boolean resolve)
4.每一个加载器保存着一张被其加载的类的map,parallelLockMap,key是类名,value是并发加载锁对象,当然,若不具有并发能力,parallelLockMap=null
5.双亲委派是由loadClass(String name, boolean resolve)方法实现,这个方法可以重写,重写了就破坏了双亲委派机制