通过源码,实例详解java类加载机制

 公司新闻     |      2019-10-14 14:18

可以看出首先接收凯发k8真人加载请求的类加载器并不一定真正加载类,可能由它的父加载器完成加载,接收加载请求的类加载器叫做初始类加载器,而完成加载的类加载器叫做定义类加载器,初始类加载器和定义类加载器可能相同也可能不同。

如果两个类:D引用了C,L1作为D的定义类加载器,在解析D时会去加载C,这个加载请求由L1接收,假设C由另一个加载器L2加载,则L1最终将加载请求委托给L2,L1就称为C的初始加载器,L2是C的定义类加载器。

下面看看ClassLoader怎么实现双亲委派加载的:

protected Class ? loadClass
 throws ClassNotFoundException
 // 一个类的加载是放在代码同步块里边的,所以不会有同一个类加载多次
 synchronized ) {
 // 首先检查该类是否已加载过
 Class ? c = findLoadedClass;
 // 如果缓存中没有找到,则按双亲委派模型加载
 if  {
 try {
 if  {
 // 如果父加载器不为null,则代理给父加载器加载
 // 父加载器在自己搜索范围内找不到该类,则抛出ClassNotFoundException
 c = parent.loadClass;
 } else {
 // 如果父加载器为null,则从引导类加载器加载过的类中
 // 找是否加载过此类,找不到返回null
 c = findBootstrapClassOrNull;
 } catch  {
 // 存在父加载器但父加载器没有找到要加载的类触发此异常
 // 只捕获不处理,交给字加载器自身去加载
 if  {
 // 如果从父加载器到顶层加载器都找不到此类,则自己来加载
 c = findClass;
 // 如果resolve指定为true,则立即进入链接阶段
 if  {
 resolveClass;
 return c;
复制代码

通过源码可以看出,所有的类都优先委派给父加载器加载,如果父加载器无法加载,则自己来加载,逻辑很简单,这样做的好处是不用层次的类交给不同的加载器去加载,如java.lang.Integer最终都是由Bootstrap ClassLoader来加载的,这样只会有一个相同类被加载。

再来说说里边调用的几个方法:

protected Object getClassLoadingLock {
 Object lock = this;
 if  {
 Object newLock = new Object;
 lock = parallelLockMap.putIfAbsent;
 if  {
 lock = newLock;
 return lock;
复制代码

该方法很简单,parallelLockMap是一个ConcurrentHashMap String, Object map对象,如果当前classloader注册为可并行加载的,则为每一个类名维护一个锁对象供synchronized使用,可并行加载不同类,否则以当前classloader作为锁对象,只能串行加载。

private Class ? findBootstrapClassOrNull
 if ) 
 return null;
 return findBootstrapClass;
复制代码
private native Class ? findBootstrapClass;
复制代码

findBootstrapClass是jvm原生实现,查找Bootstrap ClassLoader已加载的类,没有则返回null

protected Class ? findClass throws ClassNotFoundException {
 throw new ClassNotFoundException;
复制代码

findClass交给子加载器实现,我们一般重写该方法来实现自己的类加载器,这样实现的类加载器也符合双亲委派模型。当然,双亲委派的逻辑都是在loadClass实现的,可以自己重写loadClass来打破双亲委派逻辑。

自定义类加载器:

/**
 * Run: javac MyClassLoader.java Callee.java Test.java java Test
public class MyClassLoader extends ClassLoader {
 @Override
 public Class ? findClass throws ClassNotFoundException {
 try {
 InputStream is = new FileInputStream;
 byte[] data = new byte[is.available];
 is.read;
 return defineClass;
 } catch  {
 return super.loadClass;
public static void main throws Exception {
 ClassLoader myClassLoader = new MyClassLoader;
 Class ? callerClass = myClassLoader.loadClass;
 // 输出:Callee class loaded by sun.misc.Launcher$AppClassLoader
 callerClass.newInstance;
复制代码

可以看出,只需吧前面的示例方法名改为findClass就可以了,而且可以看到是由应用类加载器负责加载的,符合双亲委派模型。

再来做个实验:

// 让自定义类加载器加载/tmp目录下的类
InputStream is = new FileInputStream;
复制代码

把刚编译的Callee.class移动至/tmp下:

 mv Callee.class /tmp
复制代码

再次编译运行:

javac MyClassLoader.java java Test
复制代码

结果:

Callee class loaded by MyClassLoader
复制代码

Callee变成由自定义类加载器加载了,因为向上委托时都找不到该类,自定义加载器findClass方法起了作用。

再来做个有趣的实验:

定义一个类Caller里边调用了Callee:

public class Caller {
 public Caller {
 System.out.println.getClassLoader.getClass.getName);
 Callee callee = new Callee;
复制代码

修改Test.java,加载Caller

Class ? callerClass = myClassLoader.loadClass;
复制代码

再次编译运行:

javac MyClassLoader.java Caller.java Test.java
mv Callee.class /tmp # 保证当前目录下没有Callee.class,/tmp下有
java Test
复制代码