目录
- 前言
- 一、JVM的四种类加载器
-
- 1、引导类加载器
- 2、扩展类加载器
- 3、应用程序类加载器
- 4、自定义类加载器
- 二、类加载器的初始化
- 总结
前言
上一节(1) 类加载之流程理解记录了类加载过程中五个具体步骤的流程理解。那么对于一个需要加载的类,到底是什么东西来对它进行加载呢?
其实在程序启动之初,在加载包括核心类库在内的所有类库之前,JVM会先创建一个用C++实现的引导类加载器实例,然后用这个类加载器加载JVM启动器类"sum.misc.Launcher"并跨语言调用JAVA代码来创建实例,通过该实例调用"sum.misc.Launcher.getLauncher()"方法来初始化扩展类加载器和应用程序类加载器。上一节已经贴过流程图,在这里再次放上:

一、JVM的四种类加载器
1、引导类加载器
运行以下代码:
package com.jim.jvm.classload;
import sun.misc.Launcher;
import java.net.URL;
import java.util.List;
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println("加载List类的类加载器:" + List.class.getClassLoader());
System.out.println();
System.out.println("引导类加载器 加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
System.out.println(url);
}
}
}
得到结果如下:
加载List类的类加载器:null
引导类加载器 加载以下文件:
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/resources.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/rt.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/sunrsasign.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/jsse.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/jce.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/charsets.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/lib/jfr.jar
file:/E:/Program%20Files%20(x86)/java/jdk1.8.0_131/jre/classes
有以下问题:
-
为什么加载List类的类加载器是null呢?
答:其实在前言已经说明了,引导类加载器实例是C++对象,不是Java对象,使用Java的方法当然行不通啊。
-
引导类加载器能够加载哪些文件?
答:看输出的结果能发现,引导类加载器加载的是jdk.jre.lib目录下的类。
2、扩展类加载器
运行以下代码:
package com.jim.jvm.classload;
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println("加载RSAKeyPairGenerator类的类加载器:" + sun.security.mscapi.RSAKeyPairGenerator.class.getClassLoader());
System.out.println();
System.out.println("扩展类加载器 加载以下路径的文件:");
System.out.println(System.getProperty("java.ext.dirs"));
}
}
得到结果如下:
加载RSAKeyPairGenerator类的类加载器:sun.misc.Launcher$ExtClassLoader@29453f44
扩展类加载器 加载以下路径的文件:
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext;
C:\WINDOWS\Sun\Java\lib\ext
可以发现,扩展类加载器加载的是jdk.jre.lib.ext目录下的类。
3、应用程序类加载器
运行以下代码:
package com.jim.jvm.classload;
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println("加载自定义ClassLoaderTest类的类加载器:" + com.jim.jvm.classload.ClassLoaderTest.class.getClassLoader());
System.out.println();
System.out.println("应用程序类加载器 加载以下(路径的)文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
得到结果如下:
加载自定义ClassLoaderTest类的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
应用程序类加载器 加载以下(路径的)文件:
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\charsets.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\deploy.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\localedata.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\sunec.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\javaws.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\jce.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\jfr.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\jfxswt.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\jsse.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\management-agent.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\plugin.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\resources.jar;
E:\Program Files (x86)\java\jdk1.8.0_131\jre\lib\rt.jar;
D:\idea_projects_2019.2.4\jvm\out\production\jvm;
E:\Program Files\JetBrains\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar
可以发现,我们在写程序时,自定义的那些类一般是由应用程序类加载器来加载的。
有个问题:
-
仔细观察会发现,应用程序类加载器貌似把上面两种类加载器能加载的文件全部都加载了?那JVM设计者为什么还要分这么几种类加载器呢?
答:请看我的第三篇文章(3)JVM 类加载之双亲委派机制。
4、自定义类加载器
请看我的第三篇文章(3)JVM 类加载之双亲委派机制。
二、类加载器的初始化
前言中说到JVM会在加载类之前先初始化引导类加载器、扩展类加载器和应用程序类加载器,那么是怎样初始化的呢?引导类加载器是用C++写的,我暂时不知道,不过后面两种可以通过分析Java源码来理解。先在下图中标名初始化类加载器的时机,可结合本文第一张图看。
到底是怎么初始化扩展类加载器和应用程序类加载器的呢?从上图中可以看到,这是在调用执行"getLauncher()"函数的过程中进行的。
那么我们找到rt,jar包中的"sum.misc.Launcher"类:
找到"getLauncher()"函数:
public static Launcher getLauncher() {
return launcher;
}
返回了一个launcher,这个launcher是:
发现是一个静态变量,那么上图中的"sum.misc.Launcher.getLauncher()"就解释得通了:引导类加载器在调用Java代码创建Launcher类实例后,实例调用静态方法返回了一个在类加载过程中就已经初始化好的静态变量,那么这个静态变量是什么呢?接下来进入构造方法Launcher():
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);
}
...
}
会发现,在构造方法中创建了一个Launcher.ExtClassLoader类型的变量var1,也就是扩展类加载器,进入代码:
中的ExtClassLoader,发现是个静态内部类:
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();
}
}
...
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
...
}
其中的 “getExtClassLoader()” 是一个静态函数,返回值是:
跟进ExtClassLoader(var0),发现是ExtClassLoader的构造函数:
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
也就是说,getExtClassLoader()返回的是一个扩展类构造器实例!那么:
中的var1是一个扩展类构造器实例!
类似的,应用程序类构造器的初始化也大同小异:
public Launcher() {
...
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
...
}
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
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(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
...
}
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
综上,在执行这行代码后:
扩展类加载器和应用程序类加载器就已经被初始化了。
总结
看源码有点艰难,不过看懂后感觉,至少从逻辑上类加载器的初始化并不是很难。