首页 > 其他分享 >类加载器整理解析

类加载器整理解析

时间:2024-09-01 09:25:32浏览次数:5  
标签:java ClassLoader JVM MyClass 整理 Java 解析 加载

1.为什么要有类加载器的设计?

在不使用类加载器的场景中(假想场景),所有的类都是在程序启动时一次性加载到JVM中的。

(1)程序启动慢。需要在启动时一次性加载程序中的所有类,即使在程序运行期间根本用不到。
(2)资源浪费。未被使用的类的加载占用了JVM的内存和其他资源。
(3)类冲突。在复杂的应用程序中,可能会存在多个版本的同一个类库。如果不使用类加载器进行隔离,这些不同版本的类库可能会相互冲突,导致程序运行错误。(所有的类都将处于同一个命名空间中,这会导致版本冲突和命名冲突)
(4)安全性问题。一次性加载所有类的方式缺乏灵活性,无法根据类的来源、用途等信息进行有针对性的安全检查。如果采用一次性加载所有类的方式,当所有类都被加载到内存中时,恶意代码也可能已经混入其中。

2.什么是类加载器?

Java类加载器(Class Loader)是Java运行时环境(JRE)的一部分,负责动态地将Java类加载到Java虚拟机(JVM)的运行时环境中。当一个Java程序需要使用某个类时,JVM并不会立即去加载这个类,而是等到程序真正运行时,第一次引用到这个类的时候,才会通过类加载器来加载这个类。

类加载器必须实现的核心功能是通过全类名获取类的二进制字节流,并将类的二进制字节流解析并转换成JVM能够识别的数据结构(如Java类和接口的内部表示,以及类的元数据信息等),然后存储在JVM的方法区中,最后在内存中生成代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

下面的例子展示了Java类加载的懒加载(或称为延迟加载)特性,即类只在需要时才被加载,这种机制有助于减少程序启动时的内存占用和加载时间。

// MyClass.java  
public class MyClass {  
    public void sayHello() {  
        System.out.println("Hello, MyClass!");  
    }  
}  
  
// MainClass.java  
public class MainClass {  
    public static void main(String[] args) {  
        // 在这里,我们还没有创建MyClass的实例或调用其方法  
        // 因此,根据懒加载的原则,MyClass类还没有被加载到JVM中  
  
        // 假设这里有一些其他的逻辑...  
  
        // 当我们第一次引用MyClass时(例如,通过创建其实例或调用其静态方法),  
        // JVM会通过类加载器来加载MyClass类  
        MyClass myObject = new MyClass();  
        myObject.sayHello(); // 在这里,MyClass类被加载到JVM中,并执行sayHello方法  
  
        // 从现在起,MyClass类已经在JVM中,可以被多次使用而无需重新加载  
    }  
}

3.类加载器的实现

3.1三种类加载器:

(1)启动类加载器(Bootstrap ClassLoader)

C/C++语言实现,是JVM自带的类加载器,负责加载Java的核心库(如java.lang、java.util等),

(2) 扩展类加载器(Extension ClassLoader)

由Java语言编写,负责加载Java的扩展库,是ClassLoader类的子类

(3) 系统类加载器(System ClassLoader)

别名:应用程序类加载器(Application ClassLoader),是ClassLoader类的子类,负责加载用户类路径(CLASSPATH)上的类库(包括开发者自己编写的类以及第三方库)

3.2三种类加载器之间的组织关系:

(1)逻辑关系

启动类加载器是JAVA中所有类加载器的最顶层父加载器,但出于安全考虑,它并不继承自java.lang.ClassLoader,因此没有父加载器的概念;扩展类加载器的父加载器是启动类加载器;
系统类加载器的父加载器是扩展类加载器。

(2)什么是父加载器

在Java的类加载机制中,"父加载器"是一个相对的概念,它指的是在类加载器的双亲委派模型(Parent Delegation Model)中,当前类加载器用于委托加载类请求的上一级类加载器。

值得注意的是:类加载器的父加载器是一种包含或委托关系(这种关系通过双亲委派模型来实现),而不是Java类之间的继承关系(子加载器并不是父加载器的子类,它们之间的关系是通过委托和组合来实现的,而不是通过继承)。

(3)什么是双亲委派模型(重要面试题)

当Java程序需要使用某个类时,负责加载这个类的类加载器会首先把加载请求委托给它的父加载器,而不是自己直接加载,这种机制被称为双亲委派模型。
向上委派:实际上是查找缓存,是否加载了该类,有则直接返回,没有继续向上。
向下查找:查找加载路径,有则加载返回,没有则继续向下查找。
代码示例:
代码首先获取了ClassLoaderDemo类的类加载器,并打印出来,这通常是Application ClassLoader(系统类加载器),它负责加载用户路径(如CLASSPATH)上的类。

public class ClassLoaderDemo {  
    public static void main(String[] args) {  
        // 打印当前类的加载器  
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();  
        System.out.println("ClassLoaderDemo的类加载器是:" + classLoader);  
  
        // 获取类加载器的父加载器,并继续向上获取直到为null(Bootstrap ClassLoader)  
        while (classLoader != null) {  
            classLoader = classLoader.getParent();  
            System.out.println("父类加载器是:" + classLoader);  
        }  
  
        // 注意:上面的循环最终会打印null,因为Bootstrap ClassLoader没有父加载器  
        // Bootstrap ClassLoader是JVM自带的类加载器,负责加载Java的核心类库  
  
        // 尝试加载java.lang.String类,观察其加载器  
        ClassLoader stringClassLoader = String.class.getClassLoader();  
        System.out.println("java.lang.String的类加载器是:" + stringClassLoader);  
  
        // 通常,核心类(如java.lang.String)是由Bootstrap ClassLoader加载的  
        // Bootstrap ClassLoader不是java.lang.ClassLoader的子类,因此返回null  
    }  
}

通过循环向上获取类加载器的父加载器,直到达到Bootstrap ClassLoader(启动类加载器),Bootstrap ClassLoader不是java.lang.ClassLoader的子类,因此调用getParent()会返回null。由于java.lang.String是Java的核心类,尝试获取其类加载器,并打印出来,会发现是由Bootstrap ClassLoader加载的,而Bootstrap ClassLoader不是java.lang.ClassLoader的子类,所以调用getClassLoader()会返回null。

(4)为什么需要双亲委派模型(委托加载的好处)

(1)确保Java平台的核心类库的安全性(例如,无论哪个类加载器尝试加载java.lang.Object类,最终都会由启动类加载器来加载,从而避免了被恶意代码替换的风险)
(2)有助于实现类的唯一性,无论通过哪个类加载器加载,同一个类在JVM中只会有一个Class对象,这有助于维护Java的类型安全。

4.怎么用类加载器

4.1.获取类加载器

(1)获取系统类加载器(Application ClassLoader):这是默认的类加载器,通过ClassLoader.getSystemClassLoader()获取

(2)获取当前类的类加载器
任何类都可以通过调用getClass().getClassLoader()来获取加载该类的类加载器

(3)获取父加载器
通过调用类加载器的getParent()方法可以获取其父类加载器(如果有的话),注意,Bootstrap ClassLoader(启动类加载器)没有父类加载器,因此会返回null。

4.2.自定义类加载器

如果需要自定义类加载行为(如从非标准路径加载类、加密的类文件等),可以继承java.lang.ClassLoader类并重写其findClass(String name)方法(或loadClass(String name, boolean resolve)方法,但通常只重写findClass)。

public class MyClassLoader extends ClassLoader {  
    @Override  
    public Class<?> findClass(String name) throws ClassNotFoundException {  
        // 自定义加载逻辑,例如从文件系统、网络等位置加载类  
        byte[] classData = loadClassData(name); // 假设这个方法实现了从某处加载类数据的逻辑  
        return defineClass(name, classData, 0, classData.length);  
    }  
  
    private byte[] loadClassData(String name) {  
        // 实现加载类数据的逻辑  
        // ...  
        return null; // 示例,实际应返回类数据的字节数组  
    }  
}
4.2.使用类加载器来加载类

一旦有了类加载器(无论是系统类加载器、当前类的类加载器还是自定义类加载器),使用其来加载类

ClassLoader classLoader = MyClassLoader.class.getClassLoader(); // 获取MyClassLoader的类加载器,或直接使用自定义的MyClassLoader实例  
Class<?> clazz = classLoader.loadClass("com.example.MyClass"); // 加载指定全限定名的类  
  
// 或者,如果是自定义类加载器  
MyClassLoader myClassLoader = new MyClassLoader();  
Class<?> clazz = myClassLoader.loadClass("com.example.MyClass");

参考视频资料:https://www.bilibili.com/video/BV1HP411t7Lq?p=16&vd_source=ea0f09fc475a05984bf43c87396aa0f8

标签:java,ClassLoader,JVM,MyClass,整理,Java,解析,加载
From: https://blog.csdn.net/weixin_46624670/article/details/141738399

相关文章

  • Android 读取 XML 文件之 SAX 解析编码模板
    一、SAX解析概述SAX(SimpleAPIforXML)是一种基于事件的XML解析技术,它一边读取XML文件一边解析,占用内存少,适用于大型文件SAX解析器会触发一系列事件,例如,开始解析元素、结束解析元素、遇到字符数据等,我们只需要实现对应的事件处理器来处理这些事件即可二、SAX......
  • 可重复读隔离级别真的完全解决不可重复读问题了吗?读已提交隔离级别能避免不可重复读问
    一文带你搞懂MySQL事务的各个疑惑,不要再在脑子里一团浆糊啦!!事务的四大特性MySQL的三种日志事务的原子性是如何保证的?事务的隔离性是如何保证的?事务的持久性是如何保证的?数据库事务的隔离级别各隔离级别都各自解决了什么并发问题?什么是MVCC?读已提交和可重复读隔离级别实......
  • NoSQL:数据库领域的“新潮力量”——从起源到未来的全面解析
    引言曾几何时,关系型数据库(RDBMS)就是数据管理的“老大哥”,一统江湖,所向披靡。然而,随着大数据时代的到来,数据量像火箭般飙升,数据的形态也变得越来越“随性”,传统的关系型数据库开始有点跟不上节奏了。毕竟,当数据像一锅大杂烩一样,什么都有的时候,固定的表结构就显得有点死板了......
  • 深入解析:如何在复杂 C++ 项目中高效集成 CMake 和 Conan
    目录标题第一章:C++项目中的Conan和CMake基础架构1.1项目架构概述1.2CMake与Conan的基本角色1.2.1CMake的角色1.2.2Conan的角色1.3在项目中合理结合使用CMake和Conan1.4实例分析1.5结语第二章:C++项目中的CMake和Conan实践2.1项目结构概览......
  • 深入解析 Go 中 Map
    0前言Go语言中的map是一种内建的数据结构,用于存储键值对。它类似于其他编程语言中的哈希表或字典,提供了快速的插入、删除和查找操作。本文将深入浅出介绍map基本概念、使用方式、核心原理、性能以及最佳实践,帮助读者更好的理解和使用map。如果您觉得有帮助,请关注我,另......
  • 深入解析 Go 中 Slice
    0前言slice是一种灵活且强大的数据结构,它在功能上类似于其他编程语言中的数组,但提供了更多的灵活性。与数组不同,slice允许动态调整长度,使其在大多数场景中更加适用。本文将深入解析slice的基本概念及底层实现原理,并通过分析一些面试中常见的易错题,加深对slice的理......
  • 二、JAVA类加载机制升职加薪之旅
    文章目录一、快速梳理JAVA类加载机制1、JDK8的类加载体系2、沙箱保护机制3、Linking链接过程二、一个用类加载机制加薪的故事三、通过类加载器引入外部Jar包四、自定义类加载器实现Class代码混淆五、自定义类加载器实现热加载六、打破双亲委派,实现同类多版本共存七、使......
  • spark的SparkSubmit类关于Configuration的资源文件加载
    在阅读 SparkSubmit 源代码时,重点关注 Configuration 的资源文件的加载情况,默认通过 newConfiguration() 构造方法创建时,只会加载 core-default.xml 和core-site.xml文件,但是 SparkSubmit 中打印 Configuration 时,发现还会加载 yarn-site.xml,SparkSubmit 代码中没有......
  • zlib1.dll丢失全解析:压缩与解压修复的专家级策略
    zlib1.dll是一个与压缩和解压缩相关的动态链接库(DLL)文件,通常与zlib库的功能实现有关。这个DLL文件可能包含了处理数据压缩、资源管理和与其他系统组件交互等功能所需的函数和资源,对于确保使用zlib库的应用程序或服务的正常运行非常重要。当zlib1.dll文件丢失时,可能会导致以......
  • Spring高手之路22——AOP切面类的封装与解析
    1.AOP是如何收集切面类并封装的?在 Spring 中,AOP(Aspect-OrientedProgramming,面向切面编程)通过以下几个步骤收集切面类并进行封装:1.定义切面类:切面类通过 @Aspect 注解来标记,表示这是一个切面。在切面类中定义通知(advice),例如 @Before、@After、@Around 等,用于指定在目标方法......