5. 类加载器
- 以 JDK 8 为例
Bootstrap ClassLoader 启动类加载器
Extension ClassLoader扩展类加载器
Application ClassLoader应用程序类加载器
5.1 启动类加载器(Bootstrap ClassLoader )
public class F {
static {
System.out.println("Bootstrap ClassLoader F init");
}
}
class Load5_1 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> aClass = Class.forName("F");
System.out.println(aClass.getClassLoader());
}
}
==============在控制台运行===================
java -Xbootclasspath/a:. Load5_1
注:-Xbootclasspath 指定启动类加载的路径
/a: 需要追加的东西
. 表示追加 .
===============输出=========================
Bootstrap ClassLoader F init
null
=============分析===========================
null 表示由启动类加载器加载的
如果是 AppClassloader 或者 ExtClassLoader,表示各自的类加载器
注:-Xbootclasspath 表示设置 bootclasspath
**/a:.**表示将当前目录追加至 bootclasspath之后
可以使用以下办法替代核心类
- java -Xbootclasspath:
- java -Xbootclasspath/a:<追加路径> (后追加)
- java -Xbootclasspath/p:<追加路径> (前追加)
5.2 扩展类加载器
public class G {
static {
System.out.println("classpath G init");
}
}
class Load5_2 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> aClass = Class.forName("F");
System.out.println(aClass.getClassLoader());
}
}
===============直接IDEA运行=============================
classpath G init
sun.misc.Launcher$AppClassLoader@18b4aac2
5.3 双亲委派模式
- 所谓的双亲委派,就是值调用类加载器的 loadClass 方法时,查找类的规则
注意
这里的双亲,翻译为上级似乎更为合适,因为它们并没有继承关系
- 源码片段 ClassLoader 类里面
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 1. 检查该类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 2. 有上级的话,委派上级 loadClass
c = parent.loadClass(name, false);
} else {
// 3. 如果没有上级(ExtClassLoader),则委派 BootstrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
c = findClass(name);
// this is the defining class loader; record the stats
// 记录耗时
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
5.4 线程上下文类加载器
- 我们在使用 JDBC 时,都需要加载 Driver 驱动,不知道你注意没有,不写Class.forName(“com.mysql.jdbc.Driver”);也是可以让com.mysql.jdbc.Driver正确加载的,你知道是怎么做的吗?
源码分析:
public class DriverManager {
// 注册驱动集合
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
// 初始化驱动
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
}
- 先不看别的,看看 DriverManager 的类加载器
System.out.println(DriverManager.class.getClassLoader());
=================输出=======================
null
1. 显示 null,表示它的的类加载器是 Bootstrap ClassLoader,会到 JAVA_HOME/jre/lib 下搜索类
2. 但 JAVA_HOME/jre/lib 下显然没有 mysql-connector-java-5.1.47jar 包
3. 这样问题来啊了,在 DriverManager 的静态代码块中,怎么能正确加载 com.mysql.jdbc.Driver 呢?
- 继续看 loadInitialDrivers() 方法:
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
// 1)使用 ServiceLoader 机制加载驱动,即 SPI
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
// 2)使用 jdbc.drivers 定义的驱动名加载驱动
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
// 这里的 ClassLoader.getSystemClassLoader() 就是应用程序类加载器
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
- 再看看1)它就是大名鼎鼎的 Service Provide Interface (SPI)
约定如下,在jar包的 META-INF/services 包下,以接口全限定名名为文件,文件内容是实体类名称
- 这样就可以使用
ServiceLoader<接口类型> drivers = ServiceLoader.load(接口类型.class);
Iterator<接口类型> iterator = drivers.iterator();
while (iterator.hasNext()){
iterator.next();
}
- 来得到实现类,体现的是【面向接口编程 + 解耦】的思想,在下面的一些框架中都运用了此思想
- JDBC
- Servlet 初始化器
- Spring 容器
- Dubbo (对 SPI 进行了扩展)
- 接着看 ServiceLoader.load 方法:
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
==================线程类加载器=======================
在每个线程启动的时候,会由JVM默认会把应用程序类加载器赋值给当前线程
- 线程上下文加载器是当前线程使用的类加载器,默认就是应用程序类加载器,它的内部又是由 Class.forName 调用了线程上下文类加载器完成类加载,具体代码在 ServiceLoader 的内部类 LazyIterator 中:
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
5.5 自定义类加载器
- 什么时候需要类加载
- 想加载非 classpath 随意路径中的类文件
- 都是通过接口来使用实现,希望解耦时,常用在框架设计
- 这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常见于 Tomcat 容器
- 步骤
- 继承 ClassLoader 父类、
- 要遵循双亲委派机制,重写 findClass 方法
- 注意不是重写 loadClass 方法,否则不会走双亲委派机制
- 读取类文件字节码
- 调用父类的 defineClass 方法来加载类
- 使用者调用该类加载器的 loadClass 方法
- 示例
准备好两个类文件放入 E:\myclasspath, 它实现了 java.util.Map 接口,可以反编译看看:
public class MapImp2 extends AbstractMap {
static {
System.out.println(" MapImp init");
}
@Override
public Set<Map.Entry> entrySet() {
return null;
}
@Override
public String toString() {
return super.toString();
}
}
=================================================
public class MapImp1 extends AbstractMap {
static {
System.out.println(" MapImp1 init");
}
@Override
public Set<Entry> entrySet() {
return null;
}
@Override
public String toString() {
return super.toString();
}
}
============cmd >javap MapImp1.class 结果===================================
public class MapImp1 extends java.util.AbstractMap {
public MapImp1();
public java.util.Set<java.util.Map$Entry> entrySet();
public java.lang.String toString();
static {};
}
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @description: 自定义类加载器
* @author: Seldom
* @time: 2020/4/25 16:59
*/
public class MyLoader {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader classLoader = new MyClassLoader();
Class<?> c1 = classLoader.loadClass("MapImp1");
Class<?> c2 = classLoader.loadClass("MapImp1");
// true 说明只加载一次,地址一样
System.out.println(c1 == c2);
MyClassLoader classLoader1 = new MyClassLoader();
Class<?> c3 = classLoader1.loadClass("MapImp1");
// false 说明类加载器不同,加载到的地址也不一样(可以学学命名空间)
System.out.println(c1 == c3);
// 触发静态代码块
c1.newInstance();
}
}
class MyClassLoader extends ClassLoader {
/**
*
* @param name 类名称
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = "e:\\myclasspath\\" + name + ".class";
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
Files.copy(Paths.get(path), os);
// 得到字节数组
byte[] bytes = os.toByteArray();
// byte[] -> *.class
Class<?> aClass = defineClass(name, bytes, 0, bytes.length);
return aClass;
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("类文件没找到");
}
}
}