近来看tomcat源码,莫名有一种畅快的感觉,解决了很多疑惑,也产生了新的疑惑
什么是 ClassLoader
Java 代码要想运行,首先需要将源代码进行编译生成 .class 文件,然后 JVM 加载 .class 字节码文件到内存,而 .class 文件是怎样被加载到 JVM 中的就是Java ClassLoader 要做的事情。
那么 .class 文件什么时候会被类加载器加载到 JVM 中那?
- 比如执行 new 操作时候;
- 当我们使用 Class.forName(“包路径+类名”)
Class.forName(“包路径+类名”,ClassLoader)
ClassLoader.loadClass(“包路径+类名”)的时候就触发了类加载器去类加载对应的路径去查找 *.class,并创建 Class 对象。
java 体系的类加载器
启动类加载器(Bootstrap ClassLoader):加载对象是java的核心类库,把一些的 java 类加载到 jvm 中,它并不是我们熟悉的 ClassLoader,而是 jvm 层面由 C/C++ 实现的类加载器,负责加载 $JAVA_HOME/jre/lib 目录下 jvm 指定的类库,它是无法被 java 应用程序直接使用的
扩展类加载器(Extension Classloader):它是一个 ClassLoader 实例,父加载器是启动类加载器,它负责加载 $JAVA_HOME/jre/lib/ext 目录的类库
应用类加载器(Application ClassLoader):又叫做系统类加载器(System ClassLoader),负责加载用户类路径(-cp参数)指定的类库,可以通过 ClassLoader.getSystemClassLoader() 获取,它也是由启动类加载器加载的
自定义类加载器:应用程序根据自己的需求开发的类加载器,可以继承 ClassLoader,当然也可以不继承
下图描述了类加载器的关系图,其中自定义类加载器有N多个
我们有必要了解下关于类加载有几个重要的知识点:
在 Java 中我们用完全类名来标识一个类,而在 JVM 层面,使用完全类名 + CloassLoader 对象实例 ID 作为唯一标识,因此使用不同实例的类加载器,加载的两个同名的类,他们的类实例是不同的,并且不能强制转换
在双亲委派机制中,类加载器查找类时,是一层层往父类加载器查找的,最后才查看自己,如果都找不到则会抛出异常,而不是一层层往下找的
每个运行中的线程都有一个 CloassLoader,并且会从父线程中继承(默认是应用类加载器),在没有显式声明由哪个类加载器加载类时(比如 new 关键字),将默认由当前线程的类加载器加载该类
tomcat 类加载器
根据实际的应用场景,我们来分析下 tomcat 类加载器需要解决的几个问题
- 为了避免类冲突,每个 webapp 项目中各自使用的类库要有隔离机制
- 不同 webapp 项目支持共享某些类库
- 类加载器应该支持热插拔功能,比如对 jsp 的支持、webapp 的 reload 操作
为了解决以上问题,tomcat设计了一套类加载器,如下图所示。在 Tomcat 里面最重要的是 Common 类加载器,它的父加载器是应用程序类加载器,负责加载 c a t a l i n a . b a s e / l i b 、 {catalina.base}/lib、 catalina.base/lib、{catalina.home}/lib 目录下面所有的 .jar 文件和 .class 文件。下图的虚线部分,有 catalina 类加载器、share 类加载器,并且它们的 parent 是 common 类加载器,默认情况下被赋值为 Common 类加载器实例,即 Common 类加载器、catalina 类加载器、 share 类加载器都属于同一个实例。当然,如果我们通过修改 catalina.properties 文件的 server.loader 和 shared.loader 配置,从而指定其创建不同的类加载器
学习过程中的疑惑
1. 系统,当前与线程上下文类加载器分别是指什么?
一般指的是应用类加载器,
为啥是应用类加载器?
只有类加载器才可以加载所有类的可能
2.tomcat 启动两个项目时,会启动几个JVM?
一个
,在 JVM 层面,使用完全类名 + CloassLoader 对象实例 ID 作为唯一标识某个类,同一个jvm内,只要类加载的实例不同,就可以加载不同版本的相同类。
同一个jvm加载不同版本的全限定名相同的类
package com.example.classloadertest.verification;
import com.crossoverjie.concurrent.IFindUser;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
/**
* 同一个jvm加载不同版本的全限定名相同的类
*/
public final class Tool {
public static void main(String args[]) {
// Construct the class loader we will be using
ClassLoader classLoaderVersion1 = null;
ClassLoader classLoaderVersion2 = null;
File parentFile = new File("./");
try {
ArrayList<File> packedversion1 = new ArrayList<>();
ArrayList<File> unpacked = new ArrayList<>();
unpacked.add(new File(parentFile, "testclasses"));
packedversion1.add(new File(parentFile, "testlibversion1"));
classLoaderVersion1 = createClassLoader
(unpacked.toArray(new File[0]),
packedversion1.toArray(new File[0]),
null);
ArrayList<File> packedversion2 = new ArrayList<>();
packedversion2.add(new File(parentFile, "testlibversion2"));
classLoaderVersion2 = createClassLoader
(unpacked.toArray(new File[0]),
packedversion2.toArray(new File[0]),
null);
Class<IFindUser> ChinaNameVersion1Class = (Class<IFindUser>) classLoaderVersion1.loadClass("com.crossoverjie.concurrent.ChinaName");
Class<IFindUser> ChinaNameVersion2Class = (Class<IFindUser>) classLoaderVersion2.loadClass("com.crossoverjie.concurrent.ChinaName");
IFindUser version1Name = ChinaNameVersion1Class.newInstance();
IFindUser version2Name = ChinaNameVersion2Class.newInstance();
System.out.println(version1Name.getClass()+" "+version1Name.getName(""));
System.out.println(version2Name.getClass()+" "+version2Name.getName(""));
} catch (Exception e) {
e.printStackTrace();
}
}
public static ClassLoader createClassLoader(File unpacked[],
File packed[],
final ClassLoader parent)
throws Exception {
// Construct the "class path" for this class loader
Set<URL> set = new LinkedHashSet<>();
// Add unpacked directories
if (unpacked != null) {
for (int i = 0; i < unpacked.length; i++) {
File file = unpacked[i];
System.out.println(file.canRead()+file.getAbsolutePath());
if (!file.canRead())
continue;
file = new File(file.getCanonicalPath() + File.separator);
URL url = file.toURI().toURL();
set.add(url);
}
}
// Add packed directory JAR files
if (packed != null) {
for (int i = 0; i < packed.length; i++) {
File directory = packed[i];
if (!directory.isDirectory() || !directory.canRead())
continue;
String filenames[] = directory.list();
if (filenames == null) {
continue;
}
for (int j = 0; j < filenames.length; j++) {
String filename = filenames[j].toLowerCase(Locale.ENGLISH);
if (!filename.endsWith(".jar"))
continue;
File file = new File(directory, filenames[j]);
URL url = file.toURI().toURL();
set.add(url);
}
}
}
// Construct the class loader itself
final URL[] array = set.toArray(new URL[set.size()]);
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
}
});
}
}