浅谈Java类加载器(源码解析)
浅谈Java类加载器
参考内容:
- 深入理解Java虚拟机(JVM高级特性与最佳实践) ——周志明老师
- 尚硅谷深入理解JVM教学视频——宋红康老师
我们都知道Java的类加载器结构为下图所示(JDK8及之前,JDK9进行了模块化):
关于三层类加载器、双亲委派机制,本文不再板书,读者可自行百度。
那么在JDK的源码中,三层结构的具体实现是怎么样的呢?
Bootstrap ClassLoader(引导类加载器)
引导类加载器是由C++实现的,并非Java代码实现,所以在Java代码中是无法获取到该类加载器的。
一般大家都称类加载器分为四种(引导类、扩展类、系统类以及用户自定义的类加载器),但其实在JVM虚拟机规范中的支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader),所以扩展类和系统类也可以统称为自定义类加载器。
Extension ClassLoader(扩展类加载器)和Appclass Loader(系统类加载器)
扩展类加载器和系统类加载器都是由Java语言编写,具体实现为sum.misc.Launcher中的两个内部类ExtClassLoader和AppClassLoader实现,我们进入到LaunchLacher这个类中看看(这个类在oracle jdk是没有公开源码的,需要看具体源码的读者可以下载open jdk中查看具体源码,笔者这里就只是使用IDEA反编译后生成的代码进行解析):
首先是Laucncher的构造方法:
public Launcher() {
Launcher.ExtClassLoader var1;
try {
// 获取扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 获取系统类加载器
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);
}
}
可以看到在Launcher的构造方法中定义了一个Launcher.ExtClassLoader类型的局部变量var1(这里是反编译后的变量名),并调用Launcher.ExtClassLoader.getExtClassLoader()方法给该局部变量赋值,以及调用Launcher.AppClassLoader.getAppClassLoader(var1);给实例变量(类型为Launcher.AppClassLoader)赋值,需要注意的是,在给系统类加载器赋值时,将扩展类加载器作为参数传入到了方法中。
同时,在构造方法中,将系统类加载器设置为了当前线程的上下文类加载器,关于上下文类加载器,主要用于基础类型调用回用户代码时方法父类加载器区请求子类加载器完成类加载的行为,主要用于JDBC、JNDI等SPI服务提供者接口,这里不详细展开。
上述源码中的**getExtClassLoader()与getAppClassLoader()**方法源码如下:
getExtClassLoader()是Launcher中的内部类ExtClassLoader(扩展类加载器)的一个静态方法:
// 这是ExtClassLoader类内部的定义
private static volatile Launcher.ExtClassLoader instance;// 单例模式实例对象
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
// 从这里可以看出,ExtClassLoader是一个由double-checking形成的懒汉式单例对象
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader(); // 创建ExtClassLoader
}
}
}
return instance;
}
// createExtClassLoader()方法
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
MetaIndex.registerDirectory(var1[var3]);
}
return new Launcher.ExtClassLoader(var1); // 调用构造方法
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.getException();
}
}
// ExtClassLoader的构造方法
public ExtClassLoader(File[] var1) throws IOException {
// 此处第二个参数需要格外注意!!,我们进入父类的构造方法查看该参数是什么
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
// 父类URLClassLoader的构造方法
// 此处的第二个参数是父类构造器的引用,也就解释了为什么在调用获得ExtClassLoader的
public URLClassLoader(URL[] urls, ClassLoader parent, getParent()方法获取父类构造器为null
URLStreamHandlerFactory factory) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
acc = AccessController.getContext();
ucp = new URLClassPath(urls, factory, acc);
}
getAppClassLoader()是Launcher中的内部类AppClassLoader(系统类加载器)的一个静态方法:
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0); // 与扩展类加载器不同的是,系统类加载器并不是单例模式的
}
});
}
// AppClassLoader的构造方法
AppClassLoader(URL[] var1, ClassLoader var2) {
// 这里的var2 对应上述getAppClassLoader()方法中的var0,而var0对应的就是Launcher的构造方法中获取到的ExtClassLoader
// 在ExtClassLoader源码的分析中,我们知道这个var2代表的就是父类构造器,所以此处就是将AppClassLoader的父类设置为ExtClassLoader
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
通过上述两个方法,就可以解释为什么在获取扩展类加载器的父类时为null(即引导加载器),以及不同类加载器看似是继承(Inheritance)关系,实际上是包含关系。在下层加载器中,包含着上层加载器的引用。
ClassLoader抽象类
上述的ExtClassLoader和AppClassLoader均继承于ClassLoader类,ClassLoader抽象类也是类加载机制的基石,接下来我们就进入到该类中,看看它的一些主要方法。
-
public final classLoader getParent()
- 返回该类加载器的超类加载器
-
public Class<?>loadclass(String name) throws ClassNotFoundException
- 加载名称为name的类,返回结果为java.lang.Class类的实例。如果找不到类,则返ClassNotFoundException异常。该方法中的逻辑就是双亲委派模式的实现。
-
protected class<?> findClass(string name)throws ClassNotFoundException
- 查找二进制名称为name的类,返回结果为java.lang.Class类的实例。这是一个受保护的方法,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass()方法调用。
- 在JDK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类。但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,从前面的分析可知, findClass()方法是在loadClass()方法中被调用的,当loadclass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模式。
- 需要注意的是ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常,同时应该知道的是findClass方法通常是和defineClass方法一起使用的。一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。
-
protected final Class<?> defineClass(String name, byte[] b, int off,int len)
- 根据给定的字节数组b转换为Class的实例,off和len参数表示实际Class信息在byte数组中的位置和长度,其中byte数组b是ClassLoader从外部获取的。这是受保护的方法,只有在自定义ClassLoader子类中可以使用。
- defineClass()方法是用来将byte字节流解析郕VM能够识别的cClass对象(ClassLoader中已实现该方法逻辑),通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象。
- defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载炎的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。
-
protected final void resoiveClass(class<?> c)
- 链接指定的一个Java类。使用该方法可以使用类的Class对象创建完成的同时也被解析。前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。
-
protected final Class<?> findLoadedClass(String name)
- 查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例。这个方法是final方法,无法被修改。
-
private final ClassLoader parent;
- 它也是一个ClassLoader的实例,这个字段所表示的ClassLoader也称为这个ClassLoader的双亲。在类加载的过程中,classLoader可能会将某些请求交予自己的双亲处理。
关于这些方法,不一一展开,主要看一下loadClass()和findClass()。
loadClass()
public Class<?> loadClass(String name) throws ClassNotFoundException {
// loadClass调用重载含有两个参数的loadClass,其中第二个参数表示在加载时是否解析,默认为false
return loadClass(name, false);
}
// 含有两个参数的重载loadClass方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{// resolve:true->加载class的同时进行解析操作
synchronized (getClassLoadingLock(name)) {// 同步操作,保证只能加载一次
// 首先在缓存中判断是否已经加载同名的类
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
// 此处就是双亲委派机制的具体实现,其实就是让父类加载器先去加载。
try {
// 获取当前类加载器的父类加载器
if (parent != null) {
// 如果存在父类加载器,则调用父类加载器的loadClass进行加载(双亲委派)
c = parent.loadClass(name, false);
} else {
// parent == null:父类加载器是引导类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 当前类加载器的父类加载器未加载此类 or 当前类加载器未加载此类
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 调用当前类加载器的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;
}
}
findClass()
//在ClassLoader中的findClass()方法
rotected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
可以看到,在ClassLoader中的findCLass()方法直接抛出异常,所以具体的实现是由子类进行重写实现了;在ClassLoader的子类SecureClassLoader的子类URLClassLoader中对该方法进行了重写。
URLClassLoader中的findCLass()方法
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");// 类名路径字符串格式替换
Resource res = ucp.getResource(path, false);// 获得class源文件
if (res != null) {
try {
// 调用defineClass()方法获得要加载的类对应的Class对象,
// defineClass()的作用就是根据给定的class源文件返回一个对应的Class对象
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
最后补充一点关于数组类加载的细节
数组类的Class对象,不是由类加载器去创建的,而是在Java运行期JVM根据需要自动创建的。对于数组类的类加载器来说,是通过Class.getClassLoader()返回的,与数组当中元素类型的类加载器是一样的,如果数组当中的元素类型是基本数据类型,数组类是没有类加载器的。
本文地址:https://blog.csdn.net/weixin_42856430/article/details/110672924
下一篇: 太原端午节去哪里玩好 太原端午旅游好去处