一. 双亲委派机制
当java应用程序需要加载类的时候,调用的ClassLoader首先会委托自己的父ClassLoader去加载类,如果父ClassLoader可以加载类,那么由父ClassLoader加载类,如果父ClassLoader不能加载类,那么才由当前调用的ClassLoader去加载类
- 从JDK1.2开始,类的加载便采用了双亲委托机制,这种方式可以更好的保证Java平台的安全,例如:加载String类,由根加载器进行加载
- 除了根加载器,其他的类加载器,有且只有一个父加载器
- 父ClassLoader和当前ClassLoader不是继承关系
- 每一个Class都可以通过getClassLoader()方法,找到加载自己的ClassLoader
1. ClassLoader委托链
1.1 Bootstrap ClassLoader(根加载器)
- 没有父ClassLoader
- 从sun.boot.class.path所指定的目录加载类库
- JAVA_HOME/lib下的jar包
- 通过JVM参数-Xbootclasspath指定的目录
- 主要加载JVM的核心类,如java.lang.*
- 根加载器依赖于JVM的底层实现,属于虚拟机实现的一部分
- 获取Bootstrap ClassLoader返回null
public class JVMTest {
public static void main(String[] args) {
//String.class的ClassLoader是Bootstrap ClassLoader
System.out.println(String.class.getClassLoader());
}
}
//运行结果:
null
1.2 Extension ClassLoader(扩展加载器)
- 父加载器为Bootstrap ClassLoader
- 加载JAVA_HOME/lib/ext目录下的类库
- 或者加载java.ext.dirs的系统属性指定目录的类库
- 父类是java.lang.ClassLoader
1.3 App ClassLoader(应用加载器)
- 父加载器为ExtClassLoader
- 从java的classpath或者系统属性java.class.path所指定的目录加载类
- 自定义ClassLoader的默认父加载器
- 父类是java.lang.ClassLoader
//JVM测试类
public class JVMTest {
public static void main(String[] args) {
//JVMTest.class是在classpath下
//由AppClassLoader加载
System.out.println(JVMTest.class.getClassLoader());
}
}
//运行结果:
sun.misc.Launcher$AppClassLoader@73d16e93
2.ClassLoader初始化源码剖析
Launcher类是JVM的入口
public class Launcher {
//bootstrap ClassLoader加载路径
private static String bootClassPath = System.getProperty("sun.boot.class.path");
//构造函数
public Launcher() {
//扩展ClassLoader 初始化
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//应用ClassLoader初始化
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
//bootstrap classloader并没有具体的实例,而是JVM实现的一部分
private static class BootClassPathHolder {
static final URLClassPath bcp;
}
//应用ClassLoader
static class AppClassLoader extends URLClassLoader {
//...
}
//扩展ClassLoader
static class ExtClassLoader extends URLClassLoader {
private static volatile Launcher.ExtClassLoader instance;
//...
}
}
3.ClassLoader双亲委托机制源码剖析
public abstract class ClassLoader {
//父加载器
private final ClassLoader parent;
//当前ClassLoader加载的类列表
private final Vector> classes = new Vector<>();
//构造函数
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;
}
}
/**
* 通过名称加载类
* @param name 类全名
*/
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
/**
* 通过名称加载类
* @param name 类全名
* @param resolve 是否解析
*/
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//判断类是否被加载,最终调用的是native方法
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果父ClassLoader不为null,
//先通过父ClassLoader加载类
c = parent.loadClass(name, false);
} else {
//如果父ClassLoader为null
//通过BootstrapClassloader来加载类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
//如果父ClassLoader加载不到类
//那么当前ClassLoader继续加载
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;
}
}
4.双亲委托机制的优势
- 防止类被重复加载
- 防止JVM核心类被覆盖
二.自定义ClassLoader
1. ClassLoader抽象类中中几个重要方法
-
protected Class> loadClass(String name, boolean resolve)
- 通过类名加载类
- 调用了findLoadedClass()方法,如果返回null,那么递归找父ClassLoader,调用器loadClass()方法
- 如果最终Bootstrap ClassLoader也没有成功加载Class,就会调用当前ClassLoader的findClass方法,去查找和加载类
- protected方法,鼓励重写
-
protected Class> findClass(String name,boolean resolve)
- 通过名称查找对应的类
- 如果为true的花,在类加载之后,会对类进行解析
- 如果类已经被加载,那么返回类的Class
- 自定义ClassLoader实现该方法,可以配合defineClass()来使用
- 抛出ClassNotFoundException异常
- protected方法,鼓励重写
-
protected final Class> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain)
- 将字节码的byte数组转换为对应的Class实例
- final方法,不可以被重写
//YGX自定义ClassLoader
public class YgxClassloader extends ClassLoader {
/**
* 查找并加载类
*
* @param name 类全名
*/
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] data = loadData(name);
return defineClass(name, data, 0, data.length);
}
// 获取数据
private byte[] loadData(String name) {
File file = new File("jvm" + File.separator + name + ".class");
byte[] data = null;
try {
InputStream is = new FileInputStream(file);
data = new byte[is.available()];
is.read(data, 0, data.length);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
public static void main(String[] args) throws ClassNotFoundException {
YgxClassloader ycl = new YgxClassloader();
Class> clazz = ycl.loadClass("Hello");
System.out.println(clazz.getName());
System.out.println(clazz.getClassLoader());
}
}
//运行结果:
Hello
jvm.YgxClassloader@15db9742
2. thread.getContextClassLoader()和class.getClassLoader()
- 由于双亲委托机制,我们可以得出
- 父加载器加载的类,可以被子加载器获取和使用
- 子加载器加载的类,无法被父加载器获取和使用
- 某些服务(例如tomcat服务器)会使用自定义的ClassLoader去加载内部的类和资源
- thread.getContextClassLoader()可以获取到服务使用的自定义类加载器
- class.getClassLoader()获取到加载该clas的类加载器
- class.getClassLoader()可能获取到thread.getContextClassLoader()的父加载器,从而导致无法加载服务需要的类