1、类加载的生命周期
2、什么是类加载
通过javac将.java文件编译成.class字节码文件后,由加载器将该字节码文件加载到JVM的过程叫类加载:
- 通过全类名获取定义此类的二进制字节流;
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构;
- 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口。
3、三层类加载器
- 启动类加载器(Bootstrap ClassLoader):虚拟机的一部分,使用 C++ 实现,加载 <JAVA_HOME>/jre/lib 目录中,或者被 -Xbootclasspath 参数指定的路径中存放的类库。
- 扩展类加载器(Extension ClassLoader):独立于虚拟机外部,使用 Java 实现,加载 <JAVA_HOME>/jre/lib/ext 目录中,或者被 java.ext.dirs 系统变量所指定的路径中所有的类库。
- 应用程序加载器(Application ClassLoader):独立于虚拟机外部,使用 Java 实现,加载应用的 CLASSPATH 目录的所有类库。
import sun.misc.Launcher;
import java.net.URL;
/**
* 创建者:zjm
* 创建时间:2022/8/24
* 描述:
* 启动类加载器(Bootstrap ClassLoader),
* 扩展类加载器(Extension ClassLoader),
* 应用程序类加载器(Application ClassLoader)加载的目录
*/
public class JDKClassLoader {
public static void main(String[] args) {
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (URL url : urls) {
System.out.println(url);
}
System.out.println("ExtClassLoader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println("AppClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
输出:
BootstrapLoader加载以下文件:
file:/D:/Program%20Files/Java/jdk1.8.0_181/jre/lib/resources.jar
file:/D:/Program%20Files/Java/jdk1.8.0_181/jre/lib/rt.jar
file:/D:/Program%20Files/Java/jdk1.8.0_181/jre/lib/sunrsasign.jar
file:/D:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jsse.jar
file:/D:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jce.jar
file:/D:/Program%20Files/Java/jdk1.8.0_181/jre/lib/charsets.jar
file:/D:/Program%20Files/Java/jdk1.8.0_181/jre/lib/jfr.jar
file:/D:/Program%20Files/Java/jdk1.8.0_181/jre/classes
ExtClassLoader加载以下文件:
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext
AppClassLoader加载以下文件:
D:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;
D:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;
D:\ideaWorkSpace\out\production\jvmDemo;
D:\Program Files\JetBrains\IntelliJ IDEA 2019.2.4\lib\idea_rt.jar
进程已结束,退出代码 0
4、类加载核心源码(双亲委派如何实现):
当一个类加载器收到加载请求时,它不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载(各加载器之间不是继承关系,而是通过parent 参数来确定上下级关系),即所有加载请求最终都会委派到最顶层的加载器(Bootstrap ClassLoader)去尝试类的加载,只有当父类无法加载时,才会由子类尝试加载,如果都无法加载时,则抛出异常。
双亲委派机制实现源码:
子类并不做加载操作,只是向上委派:return super.loadClass(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));
}
}
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);
}
}
//super.loadClass(var1, var2)
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;
}
}
双亲委派机制图解:
5、为什么要使用双亲委派机制
安全:就算自己定义了一个Java.lang.String,加载器也会通过
AppClassLoader--->ExtClassLoader-->BootstrapLoader路径加载到核心jar包。可以防止核心 API库被随意篡改。
避免类重复加载:当父亲已经加载了该类时,子ClassLoader就不会再加载。
举例:
6、如何破坏双亲委派机制:
双亲委派机制是在java.lang.ClassLoader类中的loadClass方法实现的,只要自定义类加载器,重写loadClass方法就可以了,tomcat就破坏了双亲委派机制。