在剖析ClassLoader源码之前我们先来了解下类加载器
类加载器
类加载机制
我们都知道,java代码编译完成后会先形成一个.class文件,.class文件再加载到虚拟机中才能运行和使用。
虚拟机把描述类的数据从class文件加载到内存,并对数据做校验,转换,解析,初始化,最终形成可以被虚拟机直接使用的JAVA类型,这是类加载机制。
类加载器
类加载器就是对类加载机制过程的封装。
在《深入理解JAVA虚拟机》这本书中是这么定义类加载器的
“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到JAVA虚拟机外部去实现,以便让应用程序去获取所需要的类,实现这个动作的代码模块就是“类加载器”
怎么来理解上面这一句话?
我们看下类加载机制中第一步-加载的3个主要过程
- 通过一个类的全限定名来获取描述此类的二进制字节流(说白了根据这个class取一个固定的类名,通过这个类名,获得class文件的二进制流)
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据
- 在内存中生成一个代表这个类的java.lang.class对象,作为方法区的这个类的各个数据的入口
由上可知,类加载器就是将类加载的第一步在JVM外部实现了。
说到类加载器就不得不说双亲委派模型,在这我今天不讨论,网上资料太多了,只需要知道以下的核心点
一个类加载器接收到加载请求,他不会自己去加载,而是把这个请求委托给父类加载器中加载,而每个层次的加载器都是如此,最终请求都直接传送到顶层的启动类加载器,只有父类加载器无法载入的时候,子加载器才会尝试自己加载
好,那现在我来分析下顶层启动类加载器在java中的实现
ClassLoader源码分析
我们来一行行分析java.lang.class中的loadClass方法
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) { // 1.
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name); //2.
if (c == null) {
long t0 = System.nanoTime();
try {
//3
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
}
//4
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
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();
}
}
//5
if (resolve) {
resolveClass(c);
}
return c;
}
}
我用数字对代码做了几块的分离,现在我们来分析下这几个点
- 第1点 getClassLoadingLock(name) 获取这个对象的锁,使用synchronized进行同步,我们来分析下,getClassLoadingLock方法,看代码
private final ConcurrentHashMap<String, Object> parallelLockMap;
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;
}
这儿是基于concurrentHashMap来做的,concurrentHashMap是一个线程安全的map结构,putIfAbsent 表示如果有这个key那么就取出原来hashmap中的这个值,如果没有,把newLock put进去,最后返回一个锁对象。今天不具体分析concurrentHashMap,我们接着往下看
- 第2点findLoadedClass
Class<?> c = findLoadedClass(name);
protected final Class<?> findLoadedClass(String name) {
//检查类的名字,文件名是否符合加载标准
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
findLoadedClass0 是一个Native方法,看来这边有JVM来做了一些事,翻看下官方API 文档
Returns the class with the given binary name if this loader has been recorded by the Java virtual machine as an initiating loader of a class with that binary name. Otherwise null is returned.
意思大概是 如果Java虚拟机已将此加载器记录为具有该二进制名称的类的初始加载器,则返回具有给定二进制名称的类。 否则返回null。
第3点这边很简单,看代码采用递归的方式,调用loadClass方法,查询他是否有父类,如果父类不为空,则将父类加载进来,然后再去递归他的父类,也就是双亲委派模型中的像父类去传递。
第4点的代码 ,if (c == null),如果还没有发现这个类对象,则通过findClass(name)方法来加载类,前提是父类无法加载
c = findClass(name);
如果还是没有,那么返回ClassNotFoundException
findClass方法是我们重写外部加载器的关键!
- 走到这一步的时候,我们已经拿到了最后我们来看下resolveClass(),如果入参resloveClass为true的时候会走到这,而我们classLoader方法是false传入的
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
private native void resolveClass0(Class<?> c);
首先看下resolve的作用,以下引自官方文档
Links the specified class. This (misleadingly named) method may be used by a class loader to link a class. If the class c has already been linked, then this method simply returns. Otherwise, the class is linked as described in the "Execution" chapter of The Java™ Language Specification.
- 官方给出的解释是构建一个连接,如果这个类已经被连接,则不返回,如果没有,则连接起来
刚开始的不理解连接的意思,在看的源码上有这么一个注释 invoke class 这是我们在动态代理中看到的一个解释。豁然开朗,这就是动态链接,当多个类引用一个类的时候,通过链接将他们加载到一起,这样,在内存中只会有一块引用类的内存,避免了多块相同的内存块
总结
以上就是我对classLoader的分析,他的实现也是我们常说的双亲委派模型的实现
最后借用《深入理解JAVA虚拟机》中的一句话
使用双亲委派模型来组织加载器之间的关系,有一个显而易见的好处就是JAVA类随着他的类加载器一起具备了一种带有优先级的层次关系 他保护了java.lang.Object类的安全