类的加载器

一.类加载器的分类:
启动类加载器(引导类加载器,Bootstrap ClassLoader):加载一些java核心类的jar包及扩展类加载器和系统类加载器
扩展类加载器(Extension ClassLoader):主要用于加载(D:\jdk1.8\jre\lib\ext,C:\WINDOWS\Sun\Java\lib\ext)
下的jar包
系统类加载器(应用程序类加载器,Application ClassLoader):父类加载器为扩展类加载器,它负责加载环境变量classpath
或系统属性java.class.path指定路径下的类库,可以通过ClassLoader.getSystemClassLoader()来获取该类的加载器
用户自定义加载器(User ClassLoader):java开发者可以自定义类加载器来实现类库的动态加载,加载源可以是本地的jar包,也可以是
网络上的远程资源

二.ClassLoader源码解析:
重要的方法:loadClass() 、 findClass() 、 defineClass() 、 preDefineClass()

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
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 (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
// to find the class.
long t1 = System.nanoTime();
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;
}
}

三.Class.forName是一个静态方法与ClassLoader.loadClass
区别:Class.forName是一个静态方法,该方法将Class加载到内存的同时,会执行类的初始化;
ClassLoader.loadClass是一个实例方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时
才会初始化

四.双亲委派机制:
(1).定义:双亲委派机制的的具体体现为:当前ClassLoader加载类时会先检查他的父类加载器命名空间中是否加载过该类型,加载了,直接返回该类,没有加载,继续检查父类加载器,直到引导类加载器,如果父类加载器任务能够完成加载任务,则成功返回,只有父类加载器无法完成此加载任务时,才会自己去加载;
(2).为什么要使用双亲委派机制:
避免了类的重复加载,确保一个类是全局唯一性的,通过这种层级关系避免了类的重复加载;
保护程序安全,防止核心API被随意篡改(例如在src下创建java.lang包,编写自己的String类);
补充:在一个类加载器的命名空间中,同一个类型的类只会有一个;
注意:同一个类型由不同的类加载器加载,执行类型转换时会发生异常;
(3).双亲委派机制的弊端:
底层ClassLoader可以通过parent属性来获取父类的所加载的类,但是顶层ClassLoader无法访问底层ClassLoader所加载的类

五.在代码层面双亲委派机制是如何体现的:
主要体现在loadClass方法中,会先检查当前ClassLoader命名空间中是否加载过该类型,加载过,直接返回,否则会调用
当前ClassLoader的parent也就是父加载器的loadClass方法,直到parent为null,会去检查引导类加载器是否加载过该类型,如果父类加载器任务能够完成加载任务,则成功返回,只有父类加载器无法完成此加载任务时,才会自己去加载;

六.双亲委派机制对java核心类库的保护:
在findClass方法中会调用defineClass方法,该方法中会调用defineClass方法,最终会调用preDefineClass方法,该方法会对加载类进行检查,对java核心类库的保护

七.破坏双亲委派机制的行为有哪些:
(1).第一次双亲委派机制的破坏:自定义类加载器重写loadClass方法破坏双亲委派机制,解决建议:不推荐重写loadClass方法,
建议重写findClass方法,保证双亲委派机制;
(2).第二次双亲委派机制的破坏:顶层ClassLoader加载的基础类(如JNDI),无法加载第三方提供的一些代码实现,解决方法:
使用线程上下文加载器,通过Launcher类将线程的上下文加载器设置为系统类加载器,再通过系统类加载器去加载第三方代码实现;
(3).第三次双亲委派机制的破坏:代码的热替换,模块的热部署

八.线程上下文类加载器:
Launcher() —> Thread.currentThread().setContextClassLoader(this.loader);

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
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);
}
}

九.沙箱安全机制:
保证程序安全
保护Java原生的JDK代码

十.Java9新特性关于双亲委派机制的改变:
(1).扩展类加载器(Extension ClassLoader)变成了PlatformClassLoader(平台类加载器);
(2).可以通过ClassLoader.getPlatformClassLoader()获取平台类加载器;
(3).删除了URLClassLoader;
(4).引导类加载更名为BootClassLoader并集成进java API;

-XX:+TraceClassLoading:打印当前加载的类