JVM类的加载机制双亲委派,全网最详细源码

看过很多讲JVM类加载的文章,觉得不够通俗,下面是我总结的,只要有实际开发经验并了解过类加载器的,应该都能看懂了。

---------------------------------------------------------------------------------------------------------------------------------

java.exe调用底层的jvm.dll文件创建java虚拟机(C++实现)

java.exe 底层C或C++实现,通过C++实现的代码调用C、C++语言的库函数jvm.dll,创建java虚拟机。

dll文件相当于java的jar包。

C++语言的启动程序,整了一个JVM,然后JVM会创建很多java实现的类加载器,通过这些类加载器去真正调用类加载器里面loadClass方法,去加载磁盘上的字节码文件。

加载到内存之后,C语言会直接调用main方法。

-------------------分隔符----------------------------------------上面是对jvm简单印象------------------------------

记得很多年前,我们更新项目,如果小改动或者打个小补丁,都是直接替换class文件,然后重启即可。

有没有考虑过target里面的.class文件是什么样的,如何加载到内存里的,其实这个过程就是类加载。

javap -v  类名.class 能把字节码文件转换成可读性高一点的内容,但是两者是一个文件,无区别,只是可读性好一点。(想读的可以百度下字面量)

-----------------------------------------------------------------------------------------------------------------------------

类加载运行全过程
当我们用java命令运行某个类的main函数启动程序时,首先需要通过 类加载器 把主类加载到
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();
    }
}
public class User {


    private int id;

    private String 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("=======自己的加载器加载类调用方法=======");
    }
}
通过Java命令执行代码的大体流程如下:

奉上部分源码:

初始化Launcher

 launcher.getClassLoader(这里是获取的类加载器),ClassLoader loader在初始化LauNcher时就赋值了,是AppClassLoader。

 证明:

loadClass的类加载过程有如下几步:

验证:是否符合规范

准备:静态变量赋一个初始值,int为0  boolean为false

解析:用人话解释 符号引用替换为直接引用,比如main方法加载到内存中是有内存地址的(方法在内存区域的一个位置或地址),main这种静态的名称就是符号,它的位置就是代码的直接引用。(符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用)

初始化:静态变量初始化为指定值,执行静态代码块

其实这也就解释了,为什么在类里定义的静态变量会在项目启动的时候就加载好了

注意:new 对象名();   属于引用 ,会执行该类里的静态代码,

类名  变量名 = null; 不会执行类里的静态代码。

类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的
引用、对应class实例的引用 等信息。
类加载器的引用 :这个类到类加载器实例的引用
对应class实例的引用 :类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的
对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。
注意 主类在运行过程中如果使用到其它类,会逐步加载这些类。
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************

可以看出,B没有被加载。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------类加载器和双亲委派机制-----------------------------------------------

Java里有如下几种类加载器
引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等
扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包
应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那 些类
自定义加载器:负责加载用户自定义路径下的类

人话版解释如下:首先,要明白类加载器的概念

注意图中红色箭头,类加载器的名字,再对照jre目录,就能明白,

引导类加载器:C++语言实现的,加载lib目录下的jar包,引导类加载器对象不是java对象是C++生成的对象,所以看不到

扩展类加载器: 叫Ext,加载的是ext目录下的jar包

应用程序类加载器:叫App,加载的是自己写的类

Ext、App这两个都是Launcher类下的。

Launcher类是JVM启动器里面非常核心的一个类。 (Launcher中文翻译:启动器;桌面启动器;桌面;枪炮师;发射器)。

(2021年12月1日23点44分35秒开始修改)C++再创建好引导类加载器之后,它会通过C++代码调用Java代码里的Launcher类,它会初始化这个Launcher类,Launcher类就是JVM类加载器的一个启动器,它初始化这个Launcher类之后,再去加载其他类。在初始化Launcher类的过程中,会把其他类加载器全部生成出来。

类加载器例代码:

public class TestJDKClassLoader {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
        System.out.println("--------善良分割线1----------");
        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("---------善良分割线2---------");
        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("---------善良分割线3---------");
        System.out.println("extClassloader加载以下文件:");
        System.out.println(System.getProperty("java.ext.dirs"));
        System.out.println("---------善良分割线4---------");
        System.out.println("appClassLoader加载以下文件:");
        System.out.println(System.getProperty("java.class.path"));
    }
}

打印结果:
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
--------善良分割线1----------
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@548c4f57
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
---------善良分割线2---------
bootstrapLoader加载以下文件:
file:/D:/Program%20Files/Java/jdk1.8.0_152/jre/lib/resources.jar
file:/D:/Program%20Files/Java/jdk1.8.0_152/jre/lib/rt.jar
file:/D:/Program%20Files/Java/jdk1.8.0_152/jre/lib/sunrsasign.jar
file:/D:/Program%20Files/Java/jdk1.8.0_152/jre/lib/jsse.jar
file:/D:/Program%20Files/Java/jdk1.8.0_152/jre/lib/jce.jar
file:/D:/Program%20Files/Java/jdk1.8.0_152/jre/lib/charsets.jar
file:/D:/Program%20Files/Java/jdk1.8.0_152/jre/lib/jfr.jar
file:/D:/Program%20Files/Java/jdk1.8.0_152/jre/classes
---------善良分割线3---------
extClassloader加载以下文件:
D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
---------善良分割线4---------
appClassLoader加载以下文件:
D:\Program Files\Java\jdk1.8.0_152\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar;D:\other\spring-boot-timer\target\classes;D:\develop\repository\org\springframework\boot\spring-boot-starter-web\1.5.8.RELEASE\spring-boot-starter-web-1.5.8.RELEASE.jar;D:\develop\repository\org\springframework\boot\spring-boot-starter\1.5.8.RELEASE\spring-boot-starter-1.5.8.RELEASE.jar;D:\develop\repository\org\springframework\boot\spring-boot\1.5.8.RELEASE\spring-boot-1.5.8.RELEASE.jar;D:\develop\repository\org\springframework\boot\spring-boot-autoconfigure\1.5.8.RELEASE\spring-boot-autoconfigure-1.5.8.RELEASE.jar;D:\develop\repository\org\springframework\boot\spring-boot-starter-logging\1.5.8.RELEASE\spring-boot-starter-logging-1.5.8.RELEASE.jar;D:\develop\repository\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;D:\develop\repository\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar;D:\develop\repository\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;D:\develop\repository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;D:\develop\repository\org\slf4j\log4j-over-slf4j\1.7.25\log4j-over-slf4j-1.7.25.jar;D:\develop\repository\org\springframework\spring-core\4.3.12.RELEASE\spring-core-4.3.12.RELEASE.jar;D:\develop\repository\org\yaml\snakeyaml\1.17\snakeyaml-1.17.jar;D:\develop\repository\org\springframework\boot\spring-boot-starter-tomcat\1.5.8.RELEASE\spring-boot-starter-tomcat-1.5.8.RELEASE.jar;D:\develop\repository\org\apache\tomcat\embed\tomcat-embed-core\8.5.23\tomcat-embed-core-8.5.23.jar;D:\develop\repository\org\apache\tomcat\tomcat-annotations-api\8.5.23\tomcat-annotations-api-8.5.23.jar;D:\develop\repository\org\apache\tomcat\embed\tomcat-embed-el\8.5.23\tomcat-embed-el-8.5.23.jar;D:\develop\repository\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.23\tomcat-embed-websocket-8.5.23.jar;D:\develop\repository\org\hibernate\hibernate-validator\5.3.5.Final\hibernate-validator-5.3.5.Final.jar;D:\develop\repository\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;D:\develop\repository\org\jboss\logging\jboss-logging\3.3.1.Final\jboss-logging-3.3.1.Final.jar;D:\develop\repository\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;D:\develop\repository\com\fasterxml\jackson\core\jackson-databind\2.8.10\jackson-databind-2.8.10.jar;D:\develop\repository\com\fasterxml\jackson\core\jackson-annotations\2.8.0\jackson-annotations-2.8.0.jar;D:\develop\repository\com\fasterxml\jackson\core\jackson-core\2.8.10\jackson-core-2.8.10.jar;D:\develop\repository\org\springframework\spring-web\4.3.12.RELEASE\spring-web-4.3.12.RELEASE.jar;D:\develop\repository\org\springframework\spring-aop\4.3.12.RELEASE\spring-aop-4.3.12.RELEASE.jar;D:\develop\repository\org\springframework\spring-beans\4.3.12.RELEASE\spring-beans-4.3.12.RELEASE.jar;D:\develop\repository\org\springframework\spring-context\4.3.12.RELEASE\spring-context-4.3.12.RELEASE.jar;D:\develop\repository\org\springframework\spring-webmvc\4.3.12.RELEASE\spring-webmvc-4.3.12.RELEASE.jar;D:\develop\repository\org\springframework\spring-expression\4.3.12.RELEASE\spring-expression-4.3.12.RELEASE.jar;D:\develop\repository\org\jodd\jodd-all\4.3.2\jodd-all-4.3.2.jar;D:\develop\repository\com\alibaba\fastjson\1.1.26\fastjson-1.1.26.jar;D:\develop\repository\org\bouncycastle\bcprov-jdk16\1.46\bcprov-jdk16-1.46.jar;D:\develop\repository\com\whalin\Memcached-Java-Client\3.0.2\Memcached-Java-Client-3.0.2.jar;D:\develop\repository\commons-pool\commons-pool\1.6\commons-pool-1.6.jar;D:\develop\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;D:\develop\repository\org\apache\commons\commons-lang3\3.7\commons-lang3-3.7.jar;D:\develop\repository\org\apache\httpcomponents\httpclient\4.5.4\httpclient-4.5.4.jar;D:\develop\repository\org\apache\httpcomponents\httpcore\4.4.8\httpcore-4.4.8.jar;D:\develop\repository\commons-codec\commons-codec\1.10\commons-codec-1.10.jar;D:\develop\repository\org\projectlombok\lombok\1.16.18\lombok-1.16.18.jar;D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.2\lib\idea_rt.jar

 ext加载器加载的文件,在ext目录下。

-------------------------------------------------------------------------------------------------------------------------

类加载器初始化过程:
参见类运行加载全过程图可知其中会创建JVM启动器实例sun.misc.Launcher。
sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个
sun.misc.Launcher实例。
在Launcher构造方法内部,其创建了两个类加载器,分别是
sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应
用类加载器)。
JVM默认使用Launcher的 getClassLoader()方法返回的类加载器 AppClassLoader的实例加载我们
的应用程序。
1、 构造扩展类加载器,在构造的过程中将其父加载器设置为 null
2、 构造应用类加载器,在构造的过程中将其父加载器设置为 ExtClassLoader
3、 Launcher loader 属性值是 AppClassLoader ,我们一般都是用这个类加载器来加载我们自
己写的应用程序

----------------------------------------------------------------------------------------

所有的类加载器,都继承自ClassLoader。

Launcher.getLauncher();(这个还是写在这里,比较直观,以后再拆分,现在写的很细,会有些乱)
URLClassLoader后续再说,这个类会通过读取路径,把文件加载到内存中,先知道即可。

一路跟进去,注意图中箭头和红框

由此可发现:

1、在初始化Launcher类的过程中,会把ExtClassLoader和AppClassLoader初始化。

2、AppClassLoader的parent是ExtClassLoade,ExtClassLoader的的parent是null(引导类加载器,没有)。(parent是一个属性,为ClassLoader,见URLClassLoader)

其实Ext的ClassLoader算是引导类加载器,到是引导类加载器是C++写的。

---------------------------------------------------------------------------------------------------------------------------------

双亲委派机制
JVM类加载器是有亲子层级结构的,如下图

可以打印加载的内容,用图中的代码.

会发现App和Ext加载的内容,就体现了双亲委派,有些虽然打印出来了,但是不会再加载。

双亲委派流程用人话讲:

比如加载一个自己写的类A,

1、首先通过应用程序类加载器去加载,它会先检查有没有加载,如果有直接返回,如果没有就向上委托,让父加载器去加载,就这样,一直到引导类加载器,必然不会找到我自己写的A类。

2、既然引导类加载器也找不到,就会向下级委托加载,一直这样,到应用程序类加载器,它会到ClassPath下面找,这样就能找到了。

注意:这里说不是父类的意思,是父加载器

加载自定义类的都是应用程序类加载器,源码就这么写的,

来看下应用程序类加载器AppClassLoader加载类的双亲委派机制源码,AppClassLoader
的loadClass方法最终会调用其父类ClassLoader的loadClass方法,该方法的大体逻辑如下:
1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接
返回。
2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加
载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加
载。
3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的
findClass方法来完成类加载。
ClassLoader loadClass 方法,里面实现了双亲委派机制

应用程序类加载器的loadClass方法

1、Class <?> c = findLoadedClass ( name ); 检查当前类加载器是否已经加载了该类 ,
2、如果当前加载器父加载器不为空则委托父加载器加载该类

 注意,现在parent不为空,应用程序类加载器的父加载器为:扩展类加载器ExtClassLoader,会循环调用loadClass,

扩展类加载器ExtClassLoader调用loadClass,开始扩展类加载器loadClass方法。(可以看做应用程序类加载器向上委托)。

parent为null,父加载器为:引导类加载器BootstrapLoader,C++产生的对象,所以为null

如果当前加载器父加载器为空则委托引导类加载器加载该类

 走else内的findBootstrapClassOrNull(name),(可以看做扩展类加载器向上委托)。

调用本地native方法,去lib目录下加载相应类

 

必然为null,lib目录下是没有Math的,

扩展类加载器,向上委托引导类加载器加载Math类,引导类加载器没有找到,

那扩展类加载器就自己尝试加载--------------------(此处存疑,没有确定c = findClass(name);为扩展类加载器自己找)。

 向下走

都会调用URLClassLoaderfindClass方法在加载器的类路径里查找并加载该类

没有找到,会抛异常

扩展类加载器循环调用loadClass方法抛异常,被捕获,回到应用程序类加载器的loadClass方法。

应用程序类加载器的loadClass方法,继续运行,因为没有找到,c继续为null.

URLClassLoader的findClass方法,找到了Math,正常返回,这时c就赋值了

双亲委派主要部分结束。(debug视频已出,后期上链接,需要可以留言)

URLClassLoader里的findClass才是去拿文件的,装载到内存调用的是defineClass方法,跟到最后还是native本地方法。

 

这里类加载其实就有一个 双亲委派机制 ,加载某个类时会先委托父加载器寻找目标类,找不到再
委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的
类加载路径中查找并载入目标类。
比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载
器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里找了半天
没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己加载,在自己的
类加载路径里找了半天也没找到Math类,又向下退回Math类的加载请求给应用程序类加载器,
应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载

---------------------------------------------------------------------------------------------------------------------------

为什么要设计双亲委派机制?
1、沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心
API库被随意篡改
2、避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一
次,保证 被加载类的唯一性

要考虑为什么这么做:

jvm开发人员才知道,我只是猜测,

1、web应用程序95%都是自己写的类,都是应用程序类加载器加载,只有第一次加载的时候,才会循环走一遍,以后再用,直接在程序类加载器就能拿到结果。

如果都从引导类走,那95%的类,都要走一遍上面的两个类,每次加载都要走。

记住Ext是加载不到自定义类的,因为是嵌套调用,会继续用App去记载。看上面源码注意此时执行的是哪一个加载类,这个加载类的父类是谁。

-----------------------------------------------------------------------------------------------------------------------------

那为什么要用这种机制呢

1、沙箱安全机制,防止核心API库呗随意篡改

2、避免类的重复加载,保证加载类的唯一性

可以自己写个Sring类试试,参考图中类包名类名,最终会有引导类加载器返回JDK中的String,但是找不main方法,就会报错

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 载入。

---------------------------------------------------------------------------------------------------------------------------------

自定义类加载器

人话版

核心方法就是加载类的逻辑

根据位置找到这个类,再做一系列的加载逻辑。

重写findClass方法

继承ClassLoader 类

注意,ClassLoader 类中的findClass方法是一个空实现,是要重写的。

defineClass方法调用一系列本地方法,进行加载,复用即可。

自定义类加载器示例:
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是
loadClass(String, boolean),实现了 双亲委派机制 ,还有一个方法是findClass,默认实现是空
方法,所以我们自定义类加载器主要是 重写 findClass 方法
package com.jvm;

import java.io.FileInputStream;
import java.lang.reflect.Method;

/**
 * @date 2021/12/2 1:52
 */
//自定义类加载器示例
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/jvm 几级目录,将User类的复制类User1.class丢入该目录
        Class<?>  clazz = classLoader.loadClass("com.jvm.User1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj,null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }

}

运行结果:
=======自己的加载器加载类调用方法=======
com.jvm.MyClassLoaderTest$MyClassLoader

--------------------------------------------------------------------------------------------------------------------

自定义类加载器 默认的父加载器是 应用程序类加载器(源码可以看图,初始化时传的是Launcher里的ClassLoader,Launcher的ClassLoader就是AppClassLoader),自定义类加载器 也遵守双亲委派。

为什么这样设计? 答:还是为了满足双亲委派机制。

---------------------------------------------------------------------------------------------------------------------------------

打破双亲委派机制

这个先说下思路吧,今天没时间写了,改天出个视频。

直接用自定义类加载器加载,不让父加载器加载

1、在加载类的时候,调用的是loadClass方法,我复制这个方法到自己的类加载器,重写即可,修改双亲委派的规则。

2、修改加载规则,比如:如果是需要打破双亲委派机制的类,就直接用自定义加载器加载。

如果是其他类,则走双亲委派机制,如:Object类

打破双亲委派机制
再来一个沙箱安全机制示例,尝试打破双亲委派机制,用自定义类加载器加载我们自己实现的
java.lang.String.class
package com.jvm;

import java.io.FileInputStream;
import java.lang.reflect.Method;

//打破双亲委派机制示例
public class MyClassLoaderTest2 {

    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){
                throw new ClassNotFoundException();
            }
        }

      /**
       * 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
       * @param name
       * @param resolve
       * @return
       * @throws ClassNotFoundException
       **/
      protected Class<?> loadClass(String name, boolean resolve)
              throws ClassNotFoundException
      {
          synchronized (getClassLoadingLock(name)) {
              Class<?> c = findLoadedClass(name);
              if (c == null) {
                  long t0 = System.nanoTime();
                  long t1 = System.nanoTime();

                  if (!name.startsWith("com.jvm")){
                      System.out.println("name为:"+name);
                      c = this.getParent().loadClass(name);
                  }else {
                      System.out.println("name为:"+name);
                      c = findClass(name);
                  }

                      sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                      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("com.jvm.User1");
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(obj,null);
        System.out.println(clazz.getClassLoader().getClass().getName());


    }
}

运行结果:
name为:com.jvm.User1
name为:java.lang.Object
name为:java.lang.String
name为:java.lang.System
name为:java.io.PrintStream
=======自己的加载器加载类调用方法=======
com.jvm.MyClassLoaderTest2$MyClassLoader

---------------------------------------------------------------------------------------------------------------------------------

Tomcat是否打破双亲委派机制

打破了

主要是为了不同项目,隔离运行,这个改天会出一份详细的资料

https://blog.csdn.net/b416055728/article/details/121455040

-------------------------------------------------------------------------------------------------------------------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

美丽人生1989

有用的小伙伴可以打赏,多谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值