关于java:从JDK源码级别彻底剖析JVM类加载机制

50次阅读

共计 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 容器,那么它要解决什么问题:

  1. 一个 web 容器可能须要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因而要保障每个应用程序的类库都是独立的,保障互相隔离。
  2. 部署在同一个 web 容器中雷同的类库雷同的版本能够共享。否则,如果服务器有 10 个应用程序,那么要有 10 份雷同的类库加载进虚拟机。
  3. web 容器也有本人依赖的类库,不能与应用程序的类库混同。基于平安思考,应该让容器的类库和程序的类库隔离开来。
  4. 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 公布!

正文完
 0