共计 16178 个字符,预计需要花费 41 分钟才能阅读完成。
类加载运行全过程
package com.tuling.jvm;
public class Math {
public static final int initData = 666;
public static User user = new User();
public int compute() { // 一个办法对应一块栈帧内存区域
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {Math math = new Math();
math.compute();}
}
通过 Java 命令执行代码的大体流程如下:
其中 loadClass 的类加载过程有如下几步:
加载 >> 验证 >> 筹备 >> 解析 >> 初始化 >> 应用 >> 卸载
- 加载:在硬盘上查找并通过 IO 读入字节码文件,应用到类时才会加载,例如调用类的 main()办法,new 对象等等,在加载阶段会在内存中生成一个 代表这个类的 java.lang.Class 对象,作为办法区这个类的各种数据的拜访入口
- 验证:校验字节码文件的正确性
- 筹备:给类的动态变量分配内存,并赋予默认值
-
解析:将 符号援用 替换为间接援用,该阶段会把一些静态方法 (符号援用,比方 main() 办法)替换为指向数据所存内存的指针或句柄等 (间接援用),这是所谓的 动态链接 过 程 (类加载期间实现), 动静链接 是在程序运行期间实现的将符号援用替换为间接援用,下 节课会讲到动静链接
- 初始化: 对类的动态变量初始化为指定的值,执行动态代码块
留神,主类在运行过程中如果应用到其它类,会逐渐加载这些类。jar 包或 war 包里的类不是一次性全副加载的,是应用到时才加载。
public class TestDynamicLoad {
static {System.out.println("*************load TestDynamicLoad************");
}
public static void main(String[] args) {new A();
System.out.println("*************load test************");
B b = null; // B 不会加载,除非这里执行 new B()}
}
class A {
static {System.out.println("*************load A************");
}
public A() {System.out.println("*************initial A************");
}
}
class B {
static {System.out.println("*************load B************");
}
public B() {System.out.println("*************initial B************");
}
}
运行后果:*************load TestDynamicLoad************
*************load A************
*************initial A************
*************load test************
类加载器和双亲委派机制
下面的类加载过程次要是通过类加载器来实现的,Java 里有如下几品种加载器:
- 疏导类加载器:负责加载撑持 JVM 运行的位于 JRE 的 lib 目录下的外围类库,比方 rt.jar、charsets.jar 等
- 扩大类加载器:负责加载撑持 JVM 运行的位于 JRE 的 lib 目录下的 ext 扩大目录中的 JAR 类包
- 应用程序类加载器:负责加载 ClassPath 门路下的类包,次要就是加载你本人写的那些类
- 自定义加载器:负责加载用户自定义门路下的类包
看一个类加载器示例:
public class TestJDKClassLoader {public static void main(String[] args) {System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader :" + bootstrapLoader);
System.out.println("the extClassloader :" + extClassloader);
System.out.println("the appClassLoader :" + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader 加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader 加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader 加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
运行后果:null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@3764951d
the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc
bootstrapLoader 加载以下文件:file:/D:/dev/Java/jdk1.8.0_45/jre/lib/resources.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/rt.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/sunrsasign.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jsse.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jce.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/charsets.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/lib/jfr.jar
file:/D:/dev/Java/jdk1.8.0_45/jre/classes
extClassloader 加载以下文件:D:\dev\Java\jdk1.8.0_45\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
appClassLoader 加载以下文件:D:\dev\Java\jdk1.8.0_45\jre\lib\charsets.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\deploy.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\javaws.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jce.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfr.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\jsse.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\management-agent.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\plugin.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\resources.jar;D:\dev\Java\jdk1.8.0_45\jre\lib\rt.jar;D:\ideaProjects\project-all\target\classes;C:\Users\zhuge\.m2\repository\org\apache\zookeeper\zookeeper\3.4.12\zookeeper-3.4.12.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\zhuge\.m2\repository\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;C:\Users\zhuge\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\zhuge\.m2\repository\jline\jline\0.9.94\jline-0.9.94.jar;C:\Users\zhuge\.m2\repository\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;C:\Users\zhuge\.m2\repository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;C:\Users\zhuge\.m2\repository\com\google\guava\guava\22.0\guava-22.0.jar;C:\Users\zhuge\.m2\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305-1.3.9.jar;C:\Users\zhuge\.m2\repository\com\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations-2.0.18.jar;C:\Users\zhuge\.m2\repository\com\google\j2objc\j2objc-annotations\1.1\j2objc-annotations-1.1.jar;C:\Users\zhuge\.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.14\animal-sniffer-annotations-1.14.jar;D:\dev\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar
类加载器初始化过程:
参见类运行加载全过程图可知其中会创立 JVM 启动器实例 sun.misc.Launcher。
在 Launcher 构造方法外部,其创立了两个类加载器,别离是 sun.misc.Launcher.ExtClassLoader(扩大类加载器)和 sun.misc.Launcher.AppClassLoader(利用类加载器)。
JVM 默认应用 Launcher 的 getClassLoader()办法返回的类加载器 AppClassLoader 的实例加载咱们的应用程序
//Launcher 的构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
// 结构扩大类加载器,在结构的过程中将其父加载器设置为 null
var1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);
}
try {
// 结构利用类加载器,在结构的过程中将其父加载器设置为 ExtClassLoader,//Launcher 的 loader 属性值是 AppClassLoader,咱们个别都是用这个类加载器来加载咱们本人写的应用程序
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");。。。。。。// 省略一些不需关注代码
}
双亲委派机制
JVM 类加载器是有亲子层级构造的,如下图
这里类加载其实就有一个 双亲委派机制 ,加载某个类时会先委托父加载器寻找指标类,找不到再委托下层父加载器加载,如果所有父加载器在本人的加载类门路下都找不到指标类,则在本人的类加载门路中查找并载入指标类。
比方咱们的 Math 类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩大类加载器加载,扩大类加载器再委托疏导类加载器,顶层疏导类加载器在本人的类加载门路里找了半天没找到 Math 类,则向下退回加载 Math 类的申请,扩大类加载器收到回复就本人加载,在本人的类加载门路里找了半天也没找到 Math 类,又向下退回 Math 类的加载申请给应用程序类加载器,应用程序类加载器于是在本人的类加载门路里找 Math 类,后果找到了就本人加载了。。
双亲委派机制说 简略点就是,先找父亲加载,不行再由儿子本人加载
咱们来看下应用程序类加载器 AppClassLoader 加载类的双亲委派机制源码,AppClassLoader 的 loadClass 办法最终会调用其父类 ClassLoader 的 loadClass 办法,该办法的大体逻辑如下:
1. 首先,检查一下指定名称的类是否曾经加载过,如果加载过了,就不须要再加载,间接返回。
2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用 parent.loadClass(name, false);). 或者是调用 bootstrap 类加载器来加载。
3. 如果父加载器及 bootstrap 类加载器都没有找到指定的类,那么调用以后类加载器的 findClass 办法来实现类加载。
//ClassLoader 的 loadClass 办法,外面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {
// 查看以后类加载器是否曾经加载了该类
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();
// 都会调用 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.lang.String.class 类不会被加载,这样便能够避免外围 API 库被随便篡改
- 防止类的反复加载:当父亲曾经加载了该类时,就没有必要子 ClassLoader 再加载一次,保障被加载类的唯一性
看一个类加载示例:
package java.lang;
public class String {public static void main(String[] args) {System.out.println("**************My String Class**************");
}
}
运行后果:谬误: 在类 java.lang.String 中找不到 main 办法, 请将 main 办法定义为:
public static void main(String[] args)
否则 JavaFX 应用程序类必须扩大 javafx.application.Application
全盘负责委托机制
“全盘负责”是指当一个 ClassLoder 装载一个类时,除非显示的应用另外一个 ClassLoder,该类所依赖及援用的类也由这个 ClassLoder 载入。
自定义类加载器示例:
自定义类加载器只须要继承 java.lang.ClassLoader 类,该类有两个外围办法,一个是 loadClass(String, boolean),实现了 双亲委派机制 ,还有一个办法是 findClass,默认实现是空办法,所以咱们自定义类加载器次要是 重写 findClass 办法。
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("D:/test");
// D 盘创立 test/com/tuling/jvm 几级目录,将 User 类的复制类 User1.class 丢入该目录
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
运行后果:======= 本人的加载器加载类调用办法 =======
com.tuling.jvm.MyClassLoaderTest$MyClassLoader
突破双亲委派机制
再来一个沙箱平安机制示例,尝试突破双亲委派机制,用自定义类加载器加载咱们本人实现的 java.lang.String.class
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);
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 (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 {MyClassLoader classLoader = new MyClassLoader("D:/test");
// 尝试用本人改写类加载机制去加载本人写的 java.lang.String.class
Class clazz = classLoader.loadClass("java.lang.String");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
运行后果:java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
Tomcat 突破双亲委派机制
以 Tomcat 类加载为例,Tomcat 如果应用默认的双亲委派类加载机制行不行?
咱们思考一下:Tomcat 是个 web 容器,那么它要解决什么问题:
- 一个 web 容器可能须要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因而要保障每个应用程序的类库都是独立的,保障互相隔离。
- 部署在同一个 web 容器中雷同的类库雷同的版本能够共享。否则,如果服务器有 10 个应用程序,那么要有 10 份雷同的类库加载进虚拟机。
- web 容器也有本人依赖的类库,不能与应用程序的类库混同。基于平安思考,应该让容器的类库和程序的类库隔离开来。
- web 容器要反对 jsp 的批改,咱们晓得,jsp 文件最终也是要编译成 class 文件能力在虚拟机中运行,但程序运行后批改 jsp 曾经是司空见惯的事件,web 容器须要反对 jsp 批改后不必重启。
再看看咱们的问题:Tomcat 如果应用默认的双亲委派类加载机制行不行?*
答案是不行的。为什么?
第一个问题,如果应用默认的类加载器机制,那么是无奈加载两个雷同类库的不同版本的,默认的类加器是不论你是什么版本的,只在乎你的全限定类名,并且只有一份。
第二个问题,默认的类加载器是可能实现的,因为他的职责就是保障唯一性。
第三个问题和第一个问题一样。
咱们再看第四个问题,咱们想咱们要怎么实现 jsp 文件的热加载,jsp 文件其实也就是 class 文件,那么如果批改了,但类名还是一样,类加载器会间接取办法区中曾经存在的,批改后的 jsp 是不会从新加载的。那么怎么办呢?咱们能够间接卸载掉这 jsp 文件的类加载器,所以你应该想到了,每个 jsp 文件对应一个惟一的类加载器,当一个 jsp 文件批改了,就间接卸载这个 jsp 类加载器。从新创立类加载器,从新加载 jsp 文件。
Tomcat 自定义加载器详解
tomcat 的几个次要类加载器:
- commonLoader:Tomcat 最根本的类加载器,加载门路中的 class 能够被 Tomcat 容器自身以及各个 Webapp 拜访;
- catalinaLoader:Tomcat 容器公有的类加载器,加载门路中的 class 对于 Webapp 不可见;
- sharedLoader:各个 Webapp 共享的类加载器,加载门路中的 class 对于所有 Webapp 可见,然而对于 Tomcat 容器不可见;
- WebappClassLoader:各个 Webapp 公有的类加载器,加载门路中的 class 只对以后 Webapp 可见,比方加载 war 包里相干的类,每个 war 包利用都有本人的 WebappClassLoader,实现互相隔离,比方不同 war 包利用引入了不同的 spring 版本,这样实现就能加载各自的 spring 版本;
从图中的委派关系中能够看出:
CommonClassLoader 能加载的类都能够被 CatalinaClassLoader 和 SharedClassLoader 应用,从而实现了私有类库的共用,而 CatalinaClassLoader 和 SharedClassLoader 本人能加载的类则与对方互相隔离。
WebAppClassLoader 能够应用 SharedClassLoader 加载到的类,但各个 WebAppClassLoader 实例之间互相隔离。
而 JasperLoader 的加载范畴仅仅是这个 JSP 文件所编译进去的那一个.Class 文件,它呈现的目标就是为了被抛弃:当 Web 容器检测到 JSP 文件被批改时,会替换掉目前的 JasperLoader 的实例,并通过再建设一个新的 Jsp 类加载器来实现 JSP 文件的热加载性能。
tomcat 这品种加载机制违反了 java 举荐的双亲委派模型了吗?答案是:违反了。
很显然,tomcat 不是这样实现,tomcat 为了实现隔离性,没有恪守这个约定,每个 webappClassLoader 加载本人的目录下的 class 文件,不会传递给父类加载器,突破了双亲委派机制。
模仿实现 Tomcat 的 webappClassLoader 加载本人 war 包利用内不同版本类实现互相共存与隔离
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);
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 (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 非自定义的类还是走双亲委派加载
if (!name.startsWith("com.tuling.jvm")){c = this.getParent().loadClass(name);
}else{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 {MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader());
System.out.println();
MyClassLoader classLoader1 = new MyClassLoader("D:/test1");
Class clazz1 = classLoader1.loadClass("com.tuling.jvm.User1");
Object obj1 = clazz1.newInstance();
Method method1= clazz1.getDeclaredMethod("sout", null);
method1.invoke(obj1, null);
System.out.println(clazz1.getClassLoader());
}
}
运行后果:======= 本人的加载器加载类调用办法 =======
com.tuling.jvm.MyClassLoaderTest$MyClassLoader@266474c2
======= 另外一个 User1 版本:本人的加载器加载类调用办法 =======
com.tuling.jvm.MyClassLoaderTest$MyClassLoader@66d3c617
== 留神 ==:同一个 JVM 内,两个雷同包名和类名的类对象能够共存,因为他们的类加载器能够不一样,所以看两个类对象是否是同一个,除了看类的包名和类名是否都雷同之外,还须要他们的类加载器也是同一个能力认为他们是同一个。
模仿实现 Tomcat 的 JasperLoader 热加载
原理:后盾启动线程监听 jsp 文件变动,如果变动了找到该 jsp 对应的 servlet 类的加载器援用(gcroot),从新生成新的 JasperLoader 加载器赋值给援用,而后加载新的 jsp 对应的 servlet 类,之前的那个加载器因为没有 gcroot 援用了,下一次 gc 的时候会被销毁。
附下 User 类的代码:
package com.tuling.jvm;
public class User {
private int id;
private String name;
public User() {}
public User(int id, String name) {super();
this.id = id;
this.name = name;
}
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public void sout() {System.out.println("======= 本人的加载器加载类调用办法 =======");
}
}
补充:Hotspot 源码 JVM 启动执行 main 办法流程
本文由博客一文多发平台 OpenWrite 公布!