JVM学习日志(二) 类加载器

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 简述JVM类加载器

类加载器的概述

  • 作用:类加载器用于实现类的加载动作

  • 解释:对于任意一个类,都必须由加载他的类加载器和这个类本身一起共同确立其在java虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间

  • 这计划可以表达的更通俗一些:比较两个类是否"相等",只有在这两个类是由同一个类加载器加载的前提下,才有意义,否则,即使这两个类来源于同一个Class文件,被同一个java虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等

  • image-20230331103231714.png

  • image-20230331111233467.png
  • 如果想通过程序来演示上面的情况,则需要用到java提供的类加载器静态类

    java.lang.ClassLoader

    ClassLoader代码实现

    ```java
    package com.ssy.jvm.classloader;

    import java.io.InputStream;

    /**

    • @Author: DearSil
    • @Date: 2023/3/31 11:51
    • @Version: 1.0
    • @Description: Hello Description!
      /
      public class MyClassLoader extends ClassLoader{
      /*

      • 重写classLoader的加载类的方法
        *
      • @param name
      • @return
      • @throws ClassNotFoundException
        */
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {

        /**

        • name传过来的值是全类名,需要手动去分割并拼接出来class文件名称
          */
          String className = name.substring(name.lastIndexOf(".") + 1) + ".class";
          InputStream is = getClass().getResourceAsStream(name);
          if (null == is) {
              return super.loadClass(className);
          }

      try {
          //创建一个byte[]用来存储读取的数据
          byte[] bytes = new byte[is.available()];
          //通过流将数据读取到bytes数组中

          is.read(bytes);
          //调用父类方法进行读取数据到JVM
          return defineClass(className, bytes, 0, is.available());
      } catch (Exception e) {
          e.printStackTrace();
      }
      return super.loadClass(name);
  }

}


- 调用不同的类加载器加载相同的class文件,得到的class对象是不一样的

  ![image-20230331141712246.png](https://ucc.alicdn.com/pic/developer-ecology/kjxgjqvvyygtk_e0dc92d2a295484687bf2cc2cf35a348.png)


### 类加载器分类

- 类加载器分为以下几种

  | 名称                                      | 加载的那些类          | 说明                       |
  | ----------------------------------------- | --------------------- | -------------------------- |
  | BootStrap ClassLoader[启动类加载器]       | JAVA_HOME/jre/lib     | 无法直接访问               |
  | Extension ClassLoader[拓展类加载器]       | JAVA_HOME/jre/lib/ext | 上级为BootStrap,显示为null |
  | Application ClassLoader[应用程序类加载器] | classpath             | 上级为Extension            |
  | 自定义类加载器                            | 自定义                | 上级为Application          |

- 启动类加载器

  - BootStrap ClassLoader 主要负责加载机器上安装的java目录下的核心类文件,也就是JDK安装目录下jre目录下的lib目录,里面存放了一下java运行所需要的核心的类库,当JVM启动的时候,会首先依托于启动类加载器加载我们lib目录下的核心类库。当JVM启动的时候,会首先依托启动类加载器加载我们lib目录下的核心类库

- 拓展类加载器

  - Extension ClassLoader主要是加载lib目录下的ext中的文件,这里面的类用来支撑我们系统的运行

- 应用程序类加载器

  - Application ClassLoader该类加载器主要是加载classpath环境变量所指定的路径中的类,可以理解为加载我们自己写的java代码

- 自定义类加载器

  - 除了以上的三种以外,可以自定义类加载器,根据具体的需求来加载对应的类

### 双亲委派机制

- 所谓的双亲委派,就是指调用类加载器的loadClass方法时,查找类的规则

- 亲子层级结构图

  ![image-20230403141959209.png](https://ucc.alicdn.com/pic/developer-ecology/kjxgjqvvyygtk_1802764a672c4afbb8aa9b71cc02e3ae.png)


  - 基于这个亲子层级结构,就有一个双亲委派机制:先找"父类"去加载,不行的话再由儿子来加载,这样的话可以避免多层级的类加载器重复加载某些类

  - **注意**:这里的"父亲"翻译为上级似乎更为合理,因为它们并没有继承关系

  - 在所有的ClassLoader类中,都有一个属性:

    ```java
    private final ClassLoader parent;
  • 对于自定义的类加载器,通过DeBUG可以发现,他的parent是AppClassLoader,对于AppClassLoader可以发现,他的上级是ExtClassLoader,而再观察ExtClassLoader来说,他的parent属性值为null;

  • 虽然Ext类加载器的parent是Null,但是当我们的代码真正执行的时候依然会调用BootStrapClassLoader,因为ClassLoader并不是由java代码实现的了,而是C++代码,所以这里的Ext类加载器的parent为null

  • 类加载器核心源码

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

类加载器加载流程小结

image-20230404103536136.png

Tomcat类加载器设计

  • Tomcat容器类加载器设计

    image-20230404104108987.png

  • Tomcat启动加载流程图

    image-20230404104439705.png

  • 注意 : CommonClassLoader 类加载器只会加载Tomcat根目录下的lib包下的Jar包文件可以在Tomcat根目录下的 属性中进行更改

    /conf/catalina.properties - common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
    
  • SharedClassLoader类加载器默认为空,但是同样可以通过以下路径进行配置,用来指定所有WebApp可以访问的应用共享类,但是Tomcat不可访问 的应用专用共享类。

    /conf/catalina.properties - shared.loader=
    
  • CatalinaClassLoader类加载器默认也为空,但是同样可以通过以下路径进行配置,用来指定Tomcat内部课件的类,Web应用不可见

    /conf/catalina.properties - server.loader=
    
  • WebAppClassLoader类加载器用来加载WEB/INF下面的包

Tomcat类加载器的层级关系

  • WebAppClassLoader 的上级是 SharedClassLoader, 所以如果想要达到多个WebApp可以共同访问某一份依赖,可以将依赖的位置配置到Shared.loader配置中,这样可以保证所有的WebApp应用都可以访问同一份的依赖

思考

image-20230404115348139.png

  • 通过SharedClassLoader加载的Spring相关依赖如何获取WebApp相关的类?并实现Bean的管理控制?
  • 答:通过上下文类加载器

线程上下文类加载器(Context ClassLoader)

  • 线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassloader()和setContextClassLoader(ClassLoader);分别用来获取和设置上下文类加载器

  • 如果没有通过setContextLoader(ClassLoader)进行设置的话,线程将继承父类的上下文类加载器

  • 如果创建线程时还未设置,他将会从父类线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认为应用程序类加载器(在Tomcat中为webappClassLoader)

  • 因此Spring根本不会去管自己被放在哪里,他统统使用线程上下文加载器来加载类,而线程上下文加载器默认使用了WebAppClassLoder,也就是说那个WebApp应用调用了Spring,Spring就去取该应用自己的WebAppClassLoader来加载Bean

    ContextLoaderLinstener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息

  • 小结

    • 有了线程上下文类加载器,程序就可以做一些"舞弊"的事情了,父类加载器去请求子类加载器完成类加载的行为,这种行为实际上是打破了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则,但也是无可奈何的事情,java中涉及SPI的加载基本上都是采用这种方式来完成,例如JNDI,JDBC,JCE,JAXB,和JBI等

    • java提供了很多服务提供者接口,(Service provider Interface SPI),允许第三方为这些接口提供实现

    • 这些SPI的接口由java核心库来提供,而这些SPI的实现代码则是作为java应用所依赖的jar包被包含进类路径(classPath)里,SPI接口中的代码经常需要加载具体的实现类,那么问题来了,SPI的接口是java核心库的一部分,是由引导类加载器来加载的,SPI的实现类是由系统类加载器来加载的,引导类加载器是无法找到SPI的实现类的,因为按照双亲委派模型,BootStrapClassLoader无法委派AppClassLoader来加载类

      比如:在java中定义了接口java.sql.Driver,并没有具体的实现类,具体的实现类都是由各个数据库厂商来提供的

相关实践学习
通过日志服务实现云资源OSS的安全审计
本实验介绍如何通过日志服务实现云资源OSS的安全审计。
目录
相关文章
|
5月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
519 55
|
6月前
|
调度
FreeRTOS学习日志 - 第一天
这就是我的FreeRTOS学习日志 - 第一天的内容,明天继续探索这片实时操作系统的广阔海洋。+
109 12
|
5月前
|
Arthas Java 测试技术
JVM深入原理(六)(一):JVM类加载器
目录6. JVM类加载器6.1. 类加载器-概述6.2. 类加载器-执行流程6.3. 类加载器-分类(JDK8)6.3.1. JVM底层实现的类加载器6.3.1.1. 启动类加载器6.3.2. Java代码实现类的加载器6.3.2.1. 扩展类加载器6.3.2.2. 应用程序类加载器6.4. 类加载器-Arthas查看类加载器
86 0
|
11月前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
219 4
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
1146 3
|
6天前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
|
13天前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
6月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
514 6
|
9月前
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
951 166

热门文章

最新文章