真卷,这掘友的java基础竟如此牢固 类加载器源码探究

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

写在前面

jvm系列:类加载机制一文中已经对ClassLoader做了介绍,本文将从源码角度继续对ClassLoader做更深入的探索。

ClassLoader是一个抽象的类加载器对象,负责去加载类。给定了一个类的“二进制名称”,一个类加载器需要尝试去定位或者生成一个数据,该数据构成了一个定义的类。“二进制名称”:任意一个类名被提供作为ClassLoader方法的字符串参数,这个字符串形式的类名字必须是一个二进制名称,且是由java语言规范定义的,如下:

1
2
3
php复制代码"java.lang.String"
"java.net.URLClassLoader$3$1"
"java.security.KeyStore$Builder$FileBuilder$1"

类的关系图如下:

25979403-cf2c2873ecb24ecc.webp

25979403-343a2abf84fde107.webp
launcher核心类:

25979403-25ecd4393abf8147.webp

Launcher类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
java复制代码public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
//静态变量,初始化,会执行构造方法
private static Launcher launcher = newLauncher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;

public static Launcher getLauncher() {
return launcher;
}

//构造方法执行
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//初始化扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Couldnotcreateextension class loader", var10);
}
try {
//初始化应用类加载器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//设置ContextClassLoader,设置为扩展类加载器
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()中做了四件事情:

创建扩展类加载器

创建应用程序类加载器

设置ContextClassLoader

如果需要安装安全管理器 security manager

其中launcher是static的,所以初始化的时候就会创建对象,也就是触发了构造方法,所以初始化的时候就会执行上面四个步骤。
再继续看ExtClassLoader的创建中的关键几步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
scss复制代码static class ExtClassLoader extends URLClassLoader {
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
final File[] var0 = getExtDirs();
try {
return (Launcher.ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
int var1 = var0.length;
for (int var2 = 0; var2 < var1; ++var2) {
MetaIndex.registerDirectory(var0[var2]);
}
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {
throw (IOException) var2.getException();
}
}

void addExtURL(URL var1) {
super.addURL(var1);
}

public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader) null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}

private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for (int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
}

还有AppClassLoader的创建中的关键几步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码    //var1 类全名 * var2 是否连接该类
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
//通常是false,想要返回TRUE可能需要设置启动参数 lookupCacheEnabled 为true。
//为true时,具体的逻辑是C++写的
if (this.ucp.knownToNotExist(var1)) {
//如果这个类已经被这个类加载器加载,则返回这个 类,否则返回Null
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
//如果该类没有被连接,则连接,否则什么都不做
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2);
}
}

ClassLoader源码剖析

ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器),这里主要介绍ClassLoader中几个比较重要的方法。

loadClass

该方法加载指定名称(包括包名)的二进制类型,该方法在JDK1.2之后不再建议用户重写但用户可以直接调用该方法,loadClass()方法是ClassLoader类自己实现的,该方法中的逻辑就是双亲委派模式的实现,其源码如下,loadClass(String name, boolean resolve)是一个重载方法,resolve参数代表是否生成class对象的同时进行解析相关操作:

再讲一遍类的加载过程:

1、那么app会先查找是否加载过A,若有,直接返回;

2、若没有,去ext检查是否加载过A,若有,直接返回;

3、若没有,去boot检查是否加载过A,若有,直接返回;

4、若没有,那就boot加载,若在E:\Java\jdk1.8\jre\lib*.jar下找到了指定名称的类,则加载,结束;

5、若没找到,boot加载失败;

6、ext开始加载,若在E:\Java\jdk1.6\jre\lib\ext*.jar下找到了指定名称的类,则加载,结束;

7、若没找到,ext加载失败;

8、app加载,若在类路径下找到了指定名称的类,则加载,结束;

9、若没有找到,抛出异常ClassNotFoundException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
scss复制代码    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 先从缓存查找该class对象,找到就不用重新加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
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
}
if (c == null) {
// If still not found, then invoke findClass in order
// 如果都没有找到,则通过自定义实现的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;
}
}

用指定的二进制名称来加载类,这个方法的默认实现按照以下顺序查找类:
调用findLoadedClass(String)方法检查这个类是否被加载过

使用父加载器调用loadClass(String)方法,如果父加载器为Null,类加载器装载虚拟机内置的加载器调

用findClass(String)方法装载类, 如果,按照以上的步骤成功的找到对应的类,并且该方法接收的resolve参数的值为true,那么就调用resolveClass(Class)方法来处理类。

defineClass

defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象,这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象。defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象,简单例子如下:

1
2
3
4
5
6
7
8
9
10
scss复制代码 protected Class<?> findClass(String name) throws ClassNotFoundException {
// 获取类的字节数组
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
//使用defineClass生成class对象
return defineClass(name, classData, 0, classData.length);
}
}

但是如果直接调用defineClass()方法生成类的Class对象,这个类的Class对象并没有解析(还在链接阶段,需要解析链接),其解析操作需要等待初始化阶段进行。

findClass(String)

从前面的分析可知,findClass()方法是在loadClass()方法中被调用的,当loadClass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模式。需要注意的是ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常,同时应该知道的是findClass方法通常是和defineClass方法一起使用的ClassLoader类中findClass()方法源码如下:

1
2
3
4
arduino复制代码//直接抛出异常 
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}

resolveClass

resolveClass方法可以使用类的Class对象创建完成也同时被解析。前面说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用。

总结

看了很多大佬写的类加载器文章,但是自己总结一遍写一下,看一遍源码收获是实实在在的。

抽奖说明

1.本活动由掘金官方支持 详情可见juejin.cn/post/701221…

2.通过评论和文章有关的内容即可参加,要和文章内容有关哦!

3.本月的文章都会参与抽奖活动,欢迎大家多多互动!

4.除掘金官方抽奖外本人也将送出周边礼物(马克杯一个和掘金徽章若干,马克杯将送给走心评论,徽章随机抽取,数量视评论人数增加)。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%