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,并没有具体的实现类,具体的实现类都是由各个数据库厂商来提供的

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
107 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
45 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
2月前
|
存储 监控 Java
JVM进阶调优系列(8)如何手把手,逐行教她看懂GC日志?| IT男的专属浪漫
本文介绍了如何通过JVM参数打印GC日志,并通过示例代码展示了频繁YGC和FGC的场景。文章首先讲解了常见的GC日志参数,如`-XX:+PrintGCDetails`、`-XX:+PrintGCDateStamps`等,然后通过具体的JVM参数和代码示例,模拟了不同内存分配情况下的GC行为。最后,详细解析了GC日志的内容,帮助读者理解GC的执行过程和GC处理机制。
|
3月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
45 4
|
3月前
|
Arthas 监控 Java
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
87 3
|
2月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
429 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
22天前
|
监控 安全 Apache
什么是Apache日志?为什么Apache日志分析很重要?
Apache是全球广泛使用的Web服务器软件,支持超过30%的活跃网站。它通过接收和处理HTTP请求,与后端服务器通信,返回响应并记录日志,确保网页请求的快速准确处理。Apache日志分为访问日志和错误日志,对提升用户体验、保障安全及优化性能至关重要。EventLog Analyzer等工具可有效管理和分析这些日志,增强Web服务的安全性和可靠性。
|
3月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
357 3
|
1月前
|
存储 监控 安全
什么是事件日志管理系统?事件日志管理系统有哪些用处?
事件日志管理系统是IT安全的重要工具,用于集中收集、分析和解释来自组织IT基础设施各组件的事件日志,如防火墙、路由器、交换机等,帮助提升网络安全、实现主动威胁检测和促进合规性。系统支持多种日志类型,包括Windows事件日志、Syslog日志和应用程序日志,通过实时监测、告警及可视化分析,为企业提供强大的安全保障。然而,实施过程中也面临数据量大、日志管理和分析复杂等挑战。EventLog Analyzer作为一款高效工具,不仅提供实时监测与告警、可视化分析和报告功能,还支持多种合规性报告,帮助企业克服挑战,提升网络安全水平。
|
3月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1741 14
MySQL事务日志-Redo Log工作原理分析