1. 定义
来自维基百科的词条:
Java类加载器(英语:Java Classloader)是Java运行时环境(Java Runtime Environment)的一个部件,负责动态加载Java类到Java虚拟机的内存空间中。[1]类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。对学习类加载器而言,掌握Java的委派概念是很重要的。
每个Java类必须由某个类加载器装入到内存。[2]Java程序可以通过类加载器来利用外部库(即由其他作者编写的软件库)。
- 引导(Bootstrap)类加载器。由原生代码(如C语言)编写,不继承自
java.lang.ClassLoader。负责加载核心Java库[5],存储在<JAVA_HOME>/jre/lib目录中。- 扩展(Extensions)类加载器。用来在
<JAVA_HOME>/jre/lib/ext,[6]或java.ext.dirs(docs.oracle.com/javase/6/do… Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。该类由sun.misc.Launcher$ExtClassLoader实现。- Apps类加载器(也称系统类加载器)。根据 Java应用程序的类路径(
java.class.path或CLASSPATH环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader实现。每个类装载器通过组合的方式包含一个父装载器(parent class loader)。
JDK 1.2之后引入“双亲委派”方式来实现类加载器的层次调用,以尽可能保证JDK的系统API不会被用户定义的类加载器所破坏,但一些使用场景会打破这个惯例来实现必要的功能。
注1:提出一些个人见解,上面的JVM中有3个默认的类加载器这句的一些疑问,
在《Java虚拟机规范(Java SE 8)》中的5.3节中有这么一句话:
There are two kinds of class loaders: the bootstrap class loader supplied by the Java
Virtual Machine, and user-defined class loaders. Every user-defined class loader is
an instance of a subclass of the abstract class ClassLoader. Applications employ
user-defined class loaders in order to extend the manner in which the Java Virtual
Machine dynamically loads and thereby creates classes. User-defined class loaders
can be used to create classes that originate from user-defined sources. For example,
a class could be downloaded across a network, generated on the fly, or extracted
from an encrypted file.
翻译过来就是:
Java 虚拟机支持两种类加载器:Java 虚拟机提供的引导类加载器(Bootstrap Class
Loader)和用户自定义类加载器(User-Defined Class Loader)。每个用户自定义的类加
载器应该是抽象类 ClassLoader 的某个子类的实例。应用程序使用用户自定义类加载器是为了
便于扩展 Java 虚拟机的功能,支持动态加载并创建类。当然,它也可以从用户自定义的数据来源
来获取类的二进制表示并创建类。例如,用户自定义类加载器可以通过网络下载、动态产生或是从
一个加密文件中提取类的信息。
所以是维基百科说错了??????
其实是从不同的角度来理解的,在《深入理解Java虚拟机》第三版,这本书中已经给了答案,在7.4章节,
1.站在Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
2.站在Java开发人员的角度来看,类加载器就应当划分得更细致一些。自JDK 1.2以来,Java一直保持着三层类加载器、双亲委派的类加载架构,尽管这套架构在Java模块化系统出现后有了一些调整变动,但依然未改变其主体结构。
也就是说,因为Extensions class loader和System class loader这两个类加载器是用Java语言实现的,都是java.lang.ClassLoader的子类,是Java层面的代码实现。所以对于JVM一部分的Bootstrap class loader这个用C++实现的类加载器来说,这俩用Java实现的就都算是外部的、自定义的类加载器了。所以是从不同的角度来理解的。
**注2:**在维基百科的词条“JVM”中en.wikipedia.org/wiki/Java_v…
说了有很多的Java 虚拟机实现,One of Oracle's JVMs is named HotSpot; the other, inherited from [BEA Systems](https://en.wikipedia.org/wiki/BEA_Systems), is [JRockit](https://en.wikipedia.org/wiki/JRockit).
而上面的词条Java类加载器直接就说有三种类加载的实现,并没有说是具体哪种虚拟机的,而且看上去说的都是我们熟悉的HotSpot。维基百科这么的不严谨么?
其实仔细想想,注1中就说了,对于Java 虚拟机来说,只有两种,一种是Java 虚拟机自己提供的,另一种是JAVA语言层面的实现了ClassLoader类的自定义类加载器。那么既然是JAVA语言,最终都会编译成字节码,字节码跑在Java 虚拟机上。众所周知,JAVA最初是SUN公司写的,所以像类加载器这种最基础JAVA代码早就是写好的。所有的JVM实现者们,只要实现作为JVM一部分的那个Bootstrap Class Loader就可以了,对于自定义的类加载器,直接拿SUN公司实现好了的就行了。
为了验证猜想,我下载了JRockit的JDK,发现,它就是使用的和我们用的Oracle的JDK中一样的java类,sun.misc.Launcher,这里面有sun.misc.Launcher.AppClassLoader和sun.misc.Launcher.ExtClassLoader这两个JAVA语言层面的自定义类加载器。
下面从Java开发人员的角度根据JDK8来进行介绍。
2. 两种不同模型
在介绍什么是双亲委派之前,先来了解一下这三个内置的类加载。
- 启动类加载器(
Bootstrap class loader)
这个类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可,下面代码的展示的就是java.lang.Class#getClassLoader()方法的代码片段,其中的注释和代码实现都明确地说明了以null值来代表引导类加载器的约定规则。
/**
* Returns the class loader for the class. Some implementations may use
* null to represent the bootstrap class loader. This method will return
* null in such implementations if this class was loaded by the bootstrap
* class loader.
*/
public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();
if (cl == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}
return cl;
}
- 扩展类加载器(
Extensions class loader)
这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。根据“扩展类加载器”这个名称,就可以推断出这是一种Java系统类库的扩展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。
- 应用程序类加载器\系统类加载器(
Application class loader\System class loader)
这个类加载器由sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的java.lang.ClassLoader#getSystemClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径(ClassPath,java.class.path或CLASSPATH环境变量)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
官网的相关链接:
java.ext.dirs是啥?
java.class.path是啥?以及官网对于三种类的加载描述(正好对应了维基百科描述的三种类加载器)
JAVA_HOME是啥?
官网对于ClassLoader的描述。以及双亲委派的描述。
2.1JVM启动的时候,和类加载器相关的步骤
所以我们先来看看sun.misc.Launcher(这个类在rt.jar这个包中,所以在初始化BootStrapClassLoader的时候会被加载,具体怎么初始化和加载的看这篇文章huangzhilin.blog.csdn.net/article/det…)
sun.misc.Launcher这个类的代码如下:
package sun.misc;
//该类由Bootstrap Class Loader这个类加载器加载,在类加载的时候,会初始,new一个Launcher,看它的构造方法
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
//静态变量,Launcher对象本身
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
//AppClassLoader这个类加载器的实例
private ClassLoader loader;
private static URLStreamHandler fileHandler;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
// Create the extension class loader
//创建ExtClassLoader实例
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//获取AppClassLoader实例,入参是上面创建的ExtClassLoader实例
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);
}
}
//返回AppClassLoader这个类加载器的实例
public ClassLoader getClassLoader() {
return this.loader;
}
public static URLClassPath getBootstrapClassPath() {
return Launcher.BootClassPathHolder.bcp;
}
private static URL[] pathToURLs(File[] var0) {
URL[] var1 = new URL[var0.length];
for(int var2 = 0; var2 < var0.length; ++var2) {
var1[var2] = getFileURL(var0[var2]);
}
return var1;
}
private static File[] getClassPath(String var0) {
File[] var1;
if (var0 != null) {
int var2 = 0;
int var3 = 1;
boolean var4 = false;
int var5;
int var7;
for(var5 = 0; (var7 = var0.indexOf(File.pathSeparator, var5)) != -1; var5 = var7 + 1) {
++var3;
}
var1 = new File[var3];
var4 = false;
for(var5 = 0; (var7 = var0.indexOf(File.pathSeparator, var5)) != -1; var5 = var7 + 1) {
if (var7 - var5 > 0) {
var1[var2++] = new File(var0.substring(var5, var7));
} else {
var1[var2++] = new File(".");
}
}
if (var5 < var0.length()) {
var1[var2++] = new File(var0.substring(var5));
} else {
var1[var2++] = new File(".");
}
if (var2 != var3) {
File[] var6 = new File[var2];
System.arraycopy(var1, 0, var6, 0, var2);
var1 = var6;
}
} else {
var1 = new File[0];
}
return var1;
}
static URL getFileURL(File var0) {
try {
var0 = var0.getCanonicalFile();
} catch (IOException var3) {
}
try {
return ParseUtil.fileToEncodedURL(var0);
} catch (MalformedURLException var2) {
throw new InternalError(var2);
}
}
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
//var0是ExtClassLoader实例
//获取环境变量"java.class.path",也就是我们的java -cp命令的参数
//在我的电脑这个值是,算了太多了,不贴了
final String var1 = System.getProperty("java.class.path");
//获取路径中,class文件对应的File对象
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);
//调用AppClassLoader的构造函数,//var0是ExtClassLoader实例
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
AppClassLoader(URL[] var1, ClassLoader var2) {
//调用父类URLClassLoader的构造函数
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
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));
}
}
if (this.ucp.knownToNotExist(var1)) {
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);
}
}
protected PermissionCollection getPermissions(CodeSource var1) {
PermissionCollection var2 = super.getPermissions(var1);
var2.add(new RuntimePermission("exitVM"));
return var2;
}
private void appendToClassPathForInstrumentation(String var1) {
assert Thread.holdsLock(this);
super.addURL(Launcher.getFileURL(new File(var1)));
}
private static AccessControlContext getContext(File[] var0) throws MalformedURLException {
PathPermissions var1 = new PathPermissions(var0);
ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1);
AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2});
return var3;
}
static {
ClassLoader.registerAsParallelCapable();
}
}
private static class BootClassPathHolder {
static final URLClassPath bcp;
private BootClassPathHolder() {
}
static {
URL[] var0;
if (Launcher.bootClassPath != null) {
var0 = (URL[])AccessController.doPrivileged(new PrivilegedAction<URL[]>() {
public URL[] run() {
File[] var1 = Launcher.getClassPath(Launcher.bootClassPath);
int var2 = var1.length;
HashSet var3 = new HashSet();
for(int var4 = 0; var4 < var2; ++var4) {
File var5 = var1[var4];
if (!var5.isDirectory()) {
var5 = var5.getParentFile();
}
if (var5 != null && var3.add(var5)) {
MetaIndex.registerDirectory(var5);
}
}
return Launcher.pathToURLs(var1);
}
});
} else {
var0 = new URL[0];
}
bcp = new URLClassPath(var0, Launcher.factory, (AccessControlContext)null);
bcp.initLookupCache((ClassLoader)null);
}
}
static class ExtClassLoader extends URLClassLoader {
//静态变量,单例模式
private static volatile Launcher.ExtClassLoader instance;
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
//创建一个ExtClassLoader实例
instance = createExtClassLoader();
}
}
}
return instance;
}
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
//获取ExtClassLoader要加载的类文件
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
MetaIndex.registerDirectory(var1[var3]);
}
//调用ExtClassLoader的构造方法,入参是class文件数组
return new Launcher.ExtClassLoader(var1);
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.getException();
}
}
void addExtURL(URL var1) {
super.addURL(var1);
}
public ExtClassLoader(File[] var1) throws IOException {
//看它的父类URLClassLoader的构造方法
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
private static File[] getExtDirs() {
//获取系统变量"java.ext.dirs",即,这个类加载要加载的class文件的路径
//我的本地是"D:\Program Files\Java\jdk1.8.0_221\jre\lib\ext;C:\Windows\Sun\Java\lib\ext"
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) {
//创建类文件对应的File
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
private static URL[] getExtURLs(File[] var0) throws IOException {
Vector var1 = new Vector();
for(int var2 = 0; var2 < var0.length; ++var2) {
String[] var3 = var0[var2].list();
if (var3 != null) {
for(int var4 = 0; var4 < var3.length; ++var4) {
if (!var3[var4].equals("meta-index")) {
File var5 = new File(var0[var2], var3[var4]);
var1.add(Launcher.getFileURL(var5));
}
}
}
}
URL[] var6 = new URL[var1.size()];
var1.copyInto(var6);
return var6;
}
public String findLibrary(String var1) {
var1 = System.mapLibraryName(var1);
URL[] var2 = super.getURLs();
File var3 = null;
for(int var4 = 0; var4 < var2.length; ++var4) {
URI var5;
try {
var5 = var2[var4].toURI();
} catch (URISyntaxException var9) {
continue;
}
File var6 = Paths.get(var5).toFile().getParentFile();
if (var6 != null && !var6.equals(var3)) {
String var7 = VM.getSavedProperty("os.arch");
File var8;
if (var7 != null) {
var8 = new File(new File(var6, var7), var1);
if (var8.exists()) {
return var8.getAbsolutePath();
}
}
var8 = new File(var6, var1);
if (var8.exists()) {
return var8.getAbsolutePath();
}
}
var3 = var6;
}
return null;
}
private static AccessControlContext getContext(File[] var0) throws IOException {
PathPermissions var1 = new PathPermissions(var0);
ProtectionDomain var2 = new ProtectionDomain(new CodeSource(var1.getCodeBase(), (Certificate[])null), var1);
AccessControlContext var3 = new AccessControlContext(new ProtectionDomain[]{var2});
return var3;
}
static {
ClassLoader.registerAsParallelCapable();
instance = null;
}
}
private static class Factory implements URLStreamHandlerFactory {
private static String PREFIX = "sun.net.www.protocol";
private Factory() {
}
public URLStreamHandler createURLStreamHandler(String var1) {
String var2 = PREFIX + "." + var1 + ".Handler";
try {
Class var3 = Class.forName(var2);
return (URLStreamHandler)var3.newInstance();
} catch (ReflectiveOperationException var4) {
throw new InternalError("could not load " + var1 + "system protocol handler", var4);
}
}
}
}
java.net.URLClassLoader#URLClassLoader(java.net.URL[], java.lang.ClassLoader, java.net.URLStreamHandlerFactory)
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
//看它的父类java.security.SecureClassLoader#SecureClassLoader(java.lang.ClassLoader)方法
//对于ExtClassLoader,这个parent是null值
//对于AppClassLoader,这个parent是ExtClassLoader
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);
}
java.security.SecureClassLoader#SecureClassLoader(java.lang.ClassLoader)
protected SecureClassLoader(ClassLoader parent) {
//看它的父类java.lang.ClassLoader#ClassLoader(java.lang.ClassLoader)方法
//对于ExtClassLoader,这个parent是null值
//对于AppClassLoader,这个parent是ExtClassLoader
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
initialized = true;
}
java.lang.ClassLoader#ClassLoader(java.lang.ClassLoader)
protected ClassLoader(ClassLoader parent) {
//看它的重载方法java.lang.ClassLoader#ClassLoader(java.lang.Void, java.lang.ClassLoader)
//对于ExtClassLoader,这个parent是null值
//对于AppClassLoader,这个parent是ExtClassLoader
this(checkCreateClassLoader(), parent);
}
java.lang.ClassLoader#ClassLoader(java.lang.Void, java.lang.ClassLoader)
private ClassLoader(Void unused, ClassLoader parent) {
//设置当前的类加载器的父加载器
//对于ExtClassLoader,这个parent是null值
//对于AppClassLoader,这个parent是ExtClassLoader
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
**已经准备好了所有的类加载器了,虚拟机是怎么加载主类的?**www.ireader.com.cn/index.php?c…
接上面的图中的LoadMainClass()继续走,
JVM源码中会直接获取sun.launcher.LauncherHelper这个java类,然后调用它的sun.launcher.LauncherHelper#checkAndLoadMain方法
public enum LauncherHelper {
//.....................
//其实就是AppClassLoader对象的实例,ClassLoader.getSystemClassLoader()其实就是从上面的sun.misc.Launcher类中获取的
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
//.....................
public static Class<?> checkAndLoadMain(boolean var0, int var1, String var2) {
initOutput(var0);
String var3 = null;
switch(var1) {
case 1:
var3 = var2;
break;
case 2:
var3 = getMainClassFromJar(var2);
break;
default:
throw new InternalError("" + var1 + ": Unknown launch mode");
}
var3 = var3.replace('/', '.');
Class var4 = null;
try {
//这里,调用scloader的loadClass方法,scloader是一个ClassLoader对象,其实就是AppClassLoader对象,下面就是开始加载类的逻辑了
//看sun.misc.Launcher.AppClassLoader#loadClass的方法,就进入了双亲委派的逻辑了
var4 = scloader.loadClass(var3);
} catch (ClassNotFoundException | NoClassDefFoundError var8) {
if (System.getProperty("os.name", "").contains("OS X") && Normalizer.isNormalized(var3, Form.NFD)) {
try {
var4 = scloader.loadClass(Normalizer.normalize(var3, Form.NFC));
} catch (ClassNotFoundException | NoClassDefFoundError var7) {
abort(var8, "java.launcher.cls.error1", var3);
}
} else {
abort(var8, "java.launcher.cls.error1", var3);
}
}
appClass = var4;
if (!var4.equals(LauncherHelper.FXHelper.class) && !LauncherHelper.FXHelper.doesExtendFXApplication(var4)) {
validateMainClass(var4);
return var4;
} else {
LauncherHelper.FXHelper.setFXLaunchParameters(var2, var1);
return LauncherHelper.FXHelper.class;
}
}
2.2双亲委派型
JDK 9之前的Java应用都是由这三种类加载器互相配合来完成加载的,如果用户认为有必要,还可以加入自定义的类加载器来进行拓展,典型的如增加除了磁盘位置之外的Class文件来源,或者通过类加载器实现类的隔离、重载等功能。这些类加载器之间的协作关系“通常”会如上图所示。
上图中展示的各种类加载器之间的层次关系被称为类加载器的“双亲委派模型(Parents Delegation Model)”。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。
读者可能注意到前面描述这种类加载器协作关系时,笔者专门用双引号强调这是“通常”的协作关系。类加载器的双亲委派模型在JDK 1.2时期被引入,并被广泛应用于此后几乎所有的Java程序中,但它并不是一个具有强制性约束力的模型,而是Java设计者们推荐给开发者的一种类加载器实现的最佳实践。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。如果读者有兴趣的话,可以尝试去写一个与rt.jar类库中已有类重名的Java类,将会发现它可以正常编译,但永远无法被加载运行。
双亲委派模型对于保证Java程序的稳定运作极为重要,但它的实现却异常简单,用以实现双亲委派的代码只有短短十余行,全部集中在
java.lang.ClassLoader的loadClass()方法之中-------《深入理解Java虚拟机》第三版 7.4.2章节
直接看sun.misc.Launcher.AppClassLoader#loadClass方法
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++写的,所以做了什么就不大清楚了。https://www.jianshu.com/p/b95171b2527b
if (this.ucp.knownToNotExist(var1)) {
//如果这个类已经被这个类加载器加载,则返回这个 类,否则返回Null
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
//如果该类没有被link(连接),则连接,否则什么都不做
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {//一般会走这里,直接看java.lang.ClassLoader#loadClass(java.lang.String, boolean)
return super.loadClass(var1, var2);
}
}
java.lang.ClassLoader#loadClass(java.lang.String, boolean),注意这个方法是个protected限定符,它还有个public
的重载方法java.lang.ClassLoader#loadClass(java.lang.String),这个方法里面调用了protected方法。
就是在这个方法里面实现的双亲委派
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//先从缓存中查找该class对象,找到就直接返回,不用再去加载,
//最终调用的是java.lang.ClassLoader#findLoadedClass0这个本地方法,调到C++中去了
Class<?> c = findLoadedClass(name);
if (c == null) {//如果是第一次加载
long t0 = System.nanoTime();
try {//下面就是双亲委派的逻辑,啥也不是
if (parent != null) {//如果这个类加载器,有父加载器,对于ExtClassLoader,这个parent是null值,对于AppClassLoader,这个parent是ExtClassLoader
c = parent.loadClass(name, false);//对于AppClassLoader,则会调用到ExtClassLoader的loadClass方法,然鹅,ExtClassLoader并没重新实现自己的loadClass方法,所以又会调到java.lang.ClassLoader#loadClass(java.lang.String, boolean),相当于递归
} else {//对于ExtClassLoader,它parent是null值,会调到这里,嗯,就是用bootstrap class loader去加载,最后会调用到java.lang.ClassLoader#findLoadedClass0这个C++方法
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();
//则调用类加载们自定义的加载逻辑去加载类了,
//对于这两个AppClassLoader和ExtClassLoader,都是URLClassLoader的子类,所以都会走到java.net.URLClassLoader#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;
}
}
java.net.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 {
//根据要被加载的类的全限定名,把全限定名中的.都转换成/,并加上class后缀
//如:com.example.classloader.MyClassLoaderTest会被转化成com/example/classloader/MyClassLoaderTest.class
String path = name.replace('.', '/').concat(".class");
//获取这个class文件的具体地址如:file:/E:/shihy/CodeOfWork/study-shihy/target/classes/com/example/classloader/MyClassLoaderTest.class
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
//根据资源地址,真正开始调用,java.net.URLClassLoader#defineClass
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;
}
java.net.URLClassLoader#defineClass
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
URL url = res.getCodeSourceURL();
if (i != -1) {
String pkgname = name.substring(0, i);
// Check if package already loaded.
Manifest man = res.getManifest();
definePackageInternal(pkgname, man, url);
}
// Now read the class bytes and define the class
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
//最终调用的方法,最后调用到C++代码
return defineClass(name, bb, cs);
} else {
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
//最终调用的方法,最后调用到C++代码
return defineClass(name, b, 0, b.length, cs);
}
}
实现我们自己的类加载器:
双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前——即JDK 1.2面世以前的“远古”时代。由于双亲委派模型在JDK 1.2之后才被引入,但是类加载器的概念和抽象类java.lang.ClassLoader则在Java的第一个版本中就已经存在,面对已经存在的用户自定义类加载器的代码,Java设计者们引入双亲委派模型时不得不做出一些妥协,为了兼容这些已有代码,无法再以技术手段避免loadClass()被子类覆盖的可能性,**只能在JDK 1.2之后的java.lang.ClassLoader中添加一个新的protected方法findClass(),并引导用户编写的类加载逻辑时尽可能去重写这个方法,而不是在loadClass()中编写代码。**上面我们已经分析过loadClass()方法,双亲委派的具体逻辑就实现在这里面,按照loadClass()方法的逻辑,如果父类加载失败,会自动调用自己的findClass()方法来完成加载,这样既不影响用户按照自己的意愿去加载类,又可以保证新写出来的类加载器是符合双亲委派规则的。-------《深入理解Java虚拟机》第三版 7.4.3章节
由上面的加粗可知,JDK官方建议我们实现自己的类加载器的时候,希望我们保留双亲委派的规则,特意加了一个java.lang.ClassLoader#findClass方法,我们只要重写这个方法,就可以实现自己的类加载器了。这么一仔细看,java.net.URLClassLoader这个类加载器,就是一种自定义的类加载器,照着抄就得了。
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("E:\\shihy\\CodeOfWork\\study-shihy\\target\\classes\\com\\example\\classloader");
Class clazz = classLoader.loadClass("com.example.classloader.TestJDKClassLoader");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("test", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
2.3打破双亲委派
这个实现就很简单了,从2.2可知 ,打破双亲委派,只要重写protected的java.lang.ClassLoader#loadClass(java.lang.String, boolean)就可。
又因为是自定的类加载器,那么java.lang.ClassLoader#findClass还是要实现滴。
上面的自定义类加载器MyClassLoader稍微改造一下,重写个protected的java.lang.ClassLoader#loadClass(java.lang.String, boolean)就可以了,其他都一模一样。
public class MyDestroyClassLoaderTest {
static class MyDestroyClassLoader extends ClassLoader {
private String classPath;
public MyDestroyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
//这个方法没变
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
/**
* 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
*
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (!name.equals("com.example.classloader.TestJDKClassLoader")) {
c = super.loadClass(name,false);
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
public static void main(String args[]) throws Exception {
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
MyDestroyClassLoader classLoader =
new MyDestroyClassLoader("E:\\shihy\\CodeOfWork\\study-shihy\\target\\classes");
//尝试用自己改写类加载机制去加载com.example.classloader.TestJDKClassLoader
Class clazz = classLoader.loadClass("com.example.classloader.TestJDKClassLoader");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("test", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
注意:上面两个自定义的类加载器,有一点没说,那就是,自定义的类加载器的父加载器默认是sun.misc.Launcher.AppClassLoader
因为自定义的类加载器父类是java.lang.ClassLoader,所以自定义的类加载器的构造方法里面,会默认调用父类的无参构造方法。
java.lang.ClassLoader#ClassLoader()
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
查看java.lang.ClassLoader#getSystemClassLoader
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
//返回的就是AppClassLoader
return scl;
}