• JVM---类加载器


    目录

    一、什么是类加载器

    二、类加载器的分类

            启动类加载器

            Java中的默认类加载器

            扩展类加载器

            应用程序类加载器

    三、双亲委派机制

            双亲委派机制,解决的三个问题:

            双亲委派机制的作用

    四、打破双亲委派机制

            自定义类加载器

    如何自定义ClassLoader

     自定义类加载器默认的父类加载器

    打破双亲委派机制的第二种方法:JDBC案例

            JDBC案例中真的打破了双亲委派机制吗?

    五、总结


    一、什么是类加载器

            类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。类加载器 只参与加载过程中的字节码获取并加载到内存 这一部分。
            类加载器的应用场景:

    二、类加载器的分类

            类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现的。

            类加载器的设计JDK8和8之后的版本差别较大, JDK8及之前的版本中默认的类加载器有如下几种:

            类加载器的详细信息可以通过classloader命令查看:
                    classloader - 查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource

            启动类加载器

            启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C++编写的类加载器。
            默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar,resources.jar等。

            通过启动类加载器去加载用户jar 包:
                    放入jre/lib下进行扩展:
                    不推荐,尽可能不要去更改JDK安装目录中的内容,会出现即时放进去由于文件名不匹配的问题也不会正常地被加载。
                    使用参数进行扩展:
                    推荐,使用-Xbootclasspath/a:jar包目录/jar包名 进行扩展。

            Java中的默认类加载器

            扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器。
            它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录或者指定jar包将字节码文件加载到内存中。

            扩展类加载器

            扩展类加载器(Extension Class Loader)是JDK中提供的、使用Java编写的类加载器。
            默认加载Java安装目录/jre/lib/ext下的类文件。

            通过扩展类加载器去加载用户jar 包:
                    放入/jre/lib/ext下进行扩展(不推荐,尽可能不要去更改 JDK 安装目录中的内容)

                    使用参数进行扩展(推荐,使用-Djava.ext.dirs=jar包目录 进行扩展,这种方 式会覆盖掉原始目录,可以用;(windows):(macos/linux) 追加上原始目录

            应用程序类加载器

            主要是加载用户当前应用程序下的类。

    三、双亲委派机制

            每个Java实现的类加载器中保存了一个成员变量叫“父”(Parent)类加载器,可以理解为它的上级, 并不是继承关系。

            应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类加载器的parent是空。
            启动类加载器使用C++编写,没有上级类加载器。

            类加载器的继承关系可以通过classloader –t 查看:

            在类加载的过程中,每个类加载器都会先检查是否已经加载了该类,如果已经加载则直接返回,否则会将加载请求委派给父类加载器。
            如果类加载的parent null ,则会提交给启动类加载器处理。
            如果所有的父类加载器都无法加载该类,则由当前类加载器自己尝试加载。所以看上去是自顶向下尝试加载。

            第二次再去加载相同的类,仍然会向上进行委派,如果某个类加载器加载过就会直接返回。

            双亲委派机制指的是: 自底向上查找是否加载过,再由顶向下进行加载

            双亲委派机制,解决的三个问题:

            01.重复的类
            如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载?
             启动类加载器加载,根据双亲委派机制,它的优先级是最高的
            02.String类能覆盖吗
            在自己的项目中去创建一个 java.lang.String类,会被加载吗?
             不能,会交由启动类加载器加载在rt.jar包中的String类
            03.类加载器的关系
            这几个类加载器彼此之间存在关系吗?
            应用类加载器的父类加载器是扩展类加载器,扩展类加载器没有父类加载器,但是会委派给启动类加载器加载。

            双亲委派机制的作用

              1.保证类加载的安全性
            通过双亲委派机制,让顶层的类加载器去加载核心类,避免恶意代码替换JDK中的核心类库,比如 java.lang.String,确保核心类 库的完整性和安全性。  
            2.避免重复加载
            双亲委派机制可以避免同一个类被多次加载,上层的类加载器如果加载过类,就会直接返回该类,避免重复加载。

    四、打破双亲委派机制

            打破双亲委派机制的三种方式

            自定义类加载器

            一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类,
            Tomcat要保证这两个类都能加载并且它们应该是不同的类。
            如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的 MyServlet类就无法被加载了。

            Tomcat使用了自定义类加载器来实现应用之间类的隔离。
            每一个应用会有一个独立的类加载器加载对应的类。

            先来分析ClassLoader的原理,ClassLoader中包含了4个核心方法。
            双亲委派机制的核心代码就位于loadClass方法中。

            阅读双亲委派机制的核心代码,分析如何通过 自定义的类加载器 打破双亲委派机制。
            打破双亲委派机制的核心就是将下边这一段代码重新实现。

            系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader。而且我们可以根据自己的需求,对class文件进行加密和解密。

    如何自定义ClassLoader

            1.新建一个类继承自java.lang.ClassLoader,重写它的findClass方法。

            2.将class字节码数组转换为Class类的实例

            3.调用loadClass方法即可

            下面的代码就是自定义Class的例子

    1. package JVM;
    2. import java.io.ByteArrayOutputStream;
    3. import java.io.File;
    4. import java.io.FileInputStream;
    5. import java.io.IOException;
    6. public class MyClassLoader extends ClassLoader {
    7. //指定路径
    8. private String path ;
    9. public MyClassLoader(String classPath){
    10. path=classPath;
    11. }
    12. /**
    13. * 重写findClass方法
    14. * @param name 是我们这个类的全路径
    15. * @return
    16. * @throws ClassNotFoundException
    17. */
    18. @Override
    19. protected Class findClass(String name) throws ClassNotFoundException {
    20. Class log = null;
    21. // 获取该class文件字节码数组
    22. byte[] classData = getData();
    23. if (classData != null) {
    24. // 将class的字节码数组转换成Class类的实例
    25. log = defineClass(name, classData, 0, classData.length);
    26. }
    27. return log;
    28. }
    29. /**
    30. * 将class文件转化为字节码数组
    31. * @return
    32. */
    33. private byte[] getData() {
    34. File file = new File(path);
    35. if (file.exists()){
    36. FileInputStream in = null;
    37. ByteArrayOutputStream out = null;
    38. try {
    39. in = new FileInputStream(file);
    40. out = new ByteArrayOutputStream();
    41. byte[] buffer = new byte[1024];
    42. int size = 0;
    43. while ((size = in.read(buffer)) != -1) {
    44. out.write(buffer, 0, size);
    45. }
    46. } catch (IOException e) {
    47. e.printStackTrace();
    48. } finally {
    49. try {
    50. in.close();
    51. } catch (IOException e) {
    52. e.printStackTrace();
    53. }
    54. }
    55. return out.toByteArray();
    56. }else{
    57. return null;
    58. }
    59. }
    60. }

            可以在getData里面做很多事情 ,比如加密解密之类的 都是可以的。

            1、我们生成一个.class文件的测试样品

    1. public class Log {
    2. public static void main(String[] args) {
    3. System.out.println("load Log class successfully");
    4. }
    5. }

            2、我们执行命令行 javac Log.java 生成我们的Log.class文件

            3、再main函数入口 写配置参数

    1. public class ClassLoaderMain {
    2. public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
    3. //这个类class的路径
    4. String classPath = "/Users/mac/CommProjects/ClassLoaderDemo/src/com/yhk/classloaderdemo/Log.class";
    5. MyClassLoader myClassLoader = new MyClassLoader(classPath);
    6. //类的全称
    7. String packageNamePath = "com.yhk.classloaderdemo.Log";
    8. //加载Log这个class文件
    9. Class Log = myClassLoader.loadClass(packageNamePath);
    10. System.out.println("类加载器是:" + Log.getClassLoader());
    11. //利用反射获取main方法
    12. Method method = Log.getDeclaredMethod("main", String[].class);
    13. Object object = Log.newInstance();
    14. String[] arg = {"ad"};
    15. method.invoke(object, (Object) arg);
    16. }
    17. }

     自定义类加载器默认的父类加载器

             自定义类加载器父类AppClassLoader:

            以Jdk8为例,ClassLoader类中提供了构造方法设置parent的内容:

            这个构造方法由另外一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的是AppClassLoader。

            两个自定义类加载器加载相同限定名的类,不会冲突吗?
            ⚫ 不会冲突 ,在同一个Java虚拟机中,只有 相同类加载器+相同的类限定名 才会被认为是同一个类。
            ⚫ 在Arthas中使用sc –d 类名的方式查看具体的情况。

    打破双亲委派机制的第二种方法:JDBC案例

            JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql驱动、oracle驱动。

            DriverManager类位于rt.jar包中,由启动类加载器加载。

            依赖中的mysql驱动对应的类,由应用程序类加载器来加载。

            DriverManager属于rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制。

            DriverManager怎么知道jar包中要加载的驱动在哪儿?
            DriverManage使用SPI机制,最终加载jar包中对应的驱动类。

             SPI中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器。

            ⚫ 1、启动类加载器加载DriverManager。
            ⚫ 2、在初始化DriverManager时,通过SPI机制加载jar包中的myql驱动。
            ⚫ 3、SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。
            ⚫ 这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。

            JDBC案例中真的打破了双亲委派机制吗?

            打破了双亲委派机制
            这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。
            没有打破双亲委派机制
            类加载流程中,没有违反双亲委派机制。
            JDBC只是在 DriverManager 加载完之后,通过初始化阶段触发了驱动类的加载,类的加载依然遵循双亲委派机制。

    五、总结

            1.类加载器的作用是什么?
            类加载器(ClassLoader)负责在类加载过程中的字节码获取并加载到内存这一部分。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层方法将byte[]转换成方法区和堆中的数据。
            2.有几种类加载器?
                    1.启动类加载器(Bootstrap ClassLoader)加载核心类
                    2.扩展类加载器(Extension ClassLoader)加载扩展类
                    3.应用程序类加载器(Application ClassLoader)加载应用classpath中的类
                    4.自定义类加载器,重写findClass方法。
            3、什么是双亲委派机制?
                    每个Java实现的类加载器中保存了一个成员变量叫“父”(Parent)类加载器。
                    自底向上查找是否加载过,再由顶向下进行加载。避免了核心类被应用程序重写并覆盖的问题,提升了安全性。
            4、怎么打破双亲委派机制?
                    1、重写loadClass方法,不再实现双亲委派机制。
                    2、JNDI、JDBC、JCE、JAXB和JBI等框架使用了SPI机制+线程上下文类加载器。
                    3、OSGi实现了一整套类加载机制,允许同级类加载器之间互相调用。
  • 相关阅读:
    java计算机毕业设计智慧问诊系统源码+数据库+系统+lw文档
    golang for range time.Ticker 和 time.Timer时间通道使用示例 - 每隔指定时间执行一次,执行指定时长后退出执行
    引入DDP技术:英特尔网卡让数据处理更高效
    可视化滚动表格
    springCloudAlibaba之分布式网关组件---gateway
    Javascript知识【jQuery选择器】
    java基于Springboot+vue的球鞋销售商城网站 elementui
    【Cents OS7 安装 Docker以及DockerCompose】
    Google单元测试框架gtest之官方sample笔记3--值参数化测试
    Maven:命令行
  • 原文地址:https://blog.csdn.net/qq_62525547/article/details/138599680