类加载运行全过程

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"));    }}运行后果:nullsun.misc.Launcher$ExtClassLoadersun.misc.Launcher$AppClassLoaderthe bootstrapLoader : nullthe extClassloader : sun.misc.Launcher$ExtClassLoader@3764951dthe appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dcbootstrapLoader加载以下文件:file:/D:/dev/Java/jdk1.8.0_45/jre/lib/resources.jarfile:/D:/dev/Java/jdk1.8.0_45/jre/lib/rt.jarfile:/D:/dev/Java/jdk1.8.0_45/jre/lib/sunrsasign.jarfile:/D:/dev/Java/jdk1.8.0_45/jre/lib/jsse.jarfile:/D:/dev/Java/jdk1.8.0_45/jre/lib/jce.jarfile:/D:/dev/Java/jdk1.8.0_45/jre/lib/charsets.jarfile:/D:/dev/Java/jdk1.8.0_45/jre/lib/jfr.jarfile:/D:/dev/Java/jdk1.8.0_45/jre/classesextClassloader加载以下文件:D:\dev\Java\jdk1.8.0_45\jre\lib\ext;C:\Windows\Sun\Java\lib\extappClassLoader加载以下文件: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 公布!