Java 类加载器

简介: 1). 字节码和.class文件区别新建Java对象时,JVM将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath目录下(及Java工程的bin目录下)的.class文件中,类加载需要将.class文件导入硬盘,经过处理变成字节码加载到内存。
1). 字节码和.class文件区别

新建Java对象时,JVM将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath目录下(及Java工程的bin目录下)的.class文件中,类加载需要将.class文件导入硬盘,经过处理变成字节码加载到内存。
示例:

public class ClassLoaderTest {
    public static void main(String[] args) {
        // 输出ClassLoaderTest的类加载器名称
        System.out.println("ClassLoaderTest类的类加载器名称:" + ClassLoaderTest.class.getClassLoader().getClass().getName());
        System.out.println("System类的类加载器名称:" + System.class.getClassLoader());
        System.out.println("List类的类加载器名称:" + List.class.getClassLoader());
        
        // 获取ClassLoaderTest类的类加载器
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        while (null != classLoader) {
            System.out.println(classLoader.getClass().getName() + "->");
            // 获取类加载器的父类
            classLoader = classLoader.getParent();
        }
        System.out.println(classLoader);        
    }
}

打印结果:

img_c11e91befa8168ca4a504cbb83182888.png
图1.png

从打印结果可以看出ClassLoaderTest类是有AppClassLoader加载的。
因为System类,List,Map等这样的系统提供jar类都在rt.jar中,所以由BootStrap类加载器加载,因为BootStrap是祖先类,不是Java编写的,所以打印出class为null。 详情请看第三点:类加载器的委托机制

2). Java虚拟中的类加载器

Java虚拟机中可以安装多个类加载器,系统默认的有三个,每个类负责加载特定位置的类:BootStrapExtClassLoaderAppClassLoader
由于类加载器本身是Java类,也需要被类加载器加载,因此必须有一个为类加载器不是Java类,因此Bootstrap是由C/C++代码编写,被封装到JVM内核中,ExtClassLoader和AppClassLoader是Java类。
类加载器属性结构图:

img_6cbc858feddb67c6c0a327bcc0c80a96.png
图2.png

3). 类加载器的委托机制

问题:Java虚拟机要加载第一个类的时候,使用哪一个类加载器加载?
(1). 当前线程的类加载器加载线程中的第一个类(当前线程的类加载器:Thread类中有一个get/setContextClassLoader(ClassLoader);方法,可以获取/指定本线程中的类加载器)
(2). 如果类A引用了类B,Java虚拟机将使用加载类A的加载器来加载类B
(3). 可以直接调用ClassLoader.loadClass(String className)方法来指定某个类加载器去加载某个类

每个类加载器加载类时,先委托其上级类加载器来加载,当所有父加载器没有加载到类时,回到发起者类加载器,如果还是加载不了,则会抛出ClassNotFoundException。

好处:
能够很好的统一管理类加载,首先交给上级,如果上级有,就加载,这样如果之前已经加载过的类,这时候在来加载它的时候只要拿过来用就可以了,无需二次加载

4). 测试ExtClassLoader
  • 将ClassLoaderTest类打包为Jar文件,拷贝至jdk\jre\lib\ext\下路径
    img_9b394233b015567b79cd38e242435c17.png
    图3.png
  • 接下来在eclipse中运行ClassLoaderTest文件,其打印结果为
    img_18fb756164a2260108587b7c0bf008be.png
    图4.png

    打印结果验证了类加载器的委托机制.
    测试完成后记得将test.jar删除
5). 类加载器主要方法
  • 默认的类加载器
System.out.println("默认的类加载器:"+ClassLoader.getSystemClassLoader());  
img_7de9a53f8474c2c22821c1313e2ddf4b.png
图5.png
  • loadClass(String name) 源码
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        //  加锁,进行同步处理,可能多个线程加载类
        synchronized (getClassLoadingLock(name)) {
            // 检查是否已经加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 如果自定义的类加载器的parent不为null,就调用parent的loadClass进行类加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果自定义的类加载器的parent为null,就调用findBootstrapClassOrNull方法查找类,就是BootStrap类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 类未发现异常
                }

                if (c == null) {
                    // 如果仍然没有发现,就调用自己的findClass方法进行进行类加载
                    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;
        }
    }
  • defineClass,将class文件的字节数组编译成一个class对象,不能重写,内部实现为C/C++代码


    img_376b9a0e9ba60855b3c34ef36fa07a06.png
    图6.png
6). 自定义ClassLoader
  • 定义一个被加载的类
/**
 * 加载类
 * @author mazaiting
 */
public class ClassLoaderAttachment extends Date{
    private static final long serialVersionUID = -1892974244318329380L;
    
    @Override
    public String toString() {
        return "Hello ClassLoader";
    }
}
  • 自定义ClassLoader
/**
 * 自定义类加载器
 * 步骤:
 *  1. 继承ClasssLoader
 *  2. 重写findClass(String name) 方法
 * @author mazaiting
 */
public class MyClassLoader extends ClassLoader{
    /**需要加载类.class文件的目录*/
    private String classDir;
    /**无参构造*/
    public MyClassLoader() {}
    public MyClassLoader(String classDir) {
        this.classDir = classDir;
    }
    
    // 测试,先将ClassLoaderAttachment.class文件加密到工程的class_temp目录下
    public static void main(String[] args) throws Exception {
        // 配置运行参数
        // ClassLoaderAttachment.class原路径--E:\test\java_advanced\ClassLoader\bin\com\mazaiting\ClassLoaderAttachment.class 
        String srcPath = args[0];
        // ClassLoaderAttachment.class输出路径--E:\test\java_advanced\ClassLoader\class_temp\
        String desPath = args[1];
        // 获取文件名
        String desFileName = srcPath.substring(srcPath.lastIndexOf("\\") +1);
        // 配置目标文件路径
        String desPathFile = desPath + "/" + desFileName;
        FileInputStream fis = new FileInputStream(srcPath);
        FileOutputStream fos = new FileOutputStream(desPathFile);
        // 将class进行加密
        encodeAndDecode(fis, fos);
        fis.close();
        fos.close();
    }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // class文件的路径
        String classPathFile = classDir + "/" + name + ".class";
        try {
            // 将class文件进行解密
            FileInputStream fis = new FileInputStream(classPathFile);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            encodeAndDecode(fis, baos);
            byte[] classByte = baos.toByteArray();
            // 将字节流变成一个class
            return defineClass(null, classByte, 0, classByte.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return super.findClass(name);
    }
    
    /**
     * 加密解密
     * @param is 输入流
     * @param os 输出流
     * @throws IOException 
     */
    private static void encodeAndDecode(InputStream is, OutputStream os) throws IOException {
        int len;
        while ((len = is.read()) != -1) {
            len ^= 0xFF;
            os.write(len);
        }
    }
}
  • 运行:在MyClassLoader的main方法右键 -> Run As -> Run Configurations...,进行如下配置(配置前请确保ClassLoader工程目录下存在class_temp文件夹,其中第一个参数是ClassLoaderAttachment.class文件的源路径,第二个参数是加密后存放的目录),点击Run运行,此时在E:\test\java_advanced\ClassLoader\class_temp将会生成加密后的ClassLoaderAttachment.class文件
    img_8ed55ecb747c1b26ea92c6555bbc7547.png
    图7.png
  • 使用,在ClassLoaderTest的main函数中应用
public class ClassLoaderTest {
    public static void main(String[] args) {
        try {
            // 加载类
            Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
            // 创建实例
            Date date = (Date) classDate.newInstance();
            // 输出ClassLoaderAttachment类的加载器名称
            System.out.println("ClassLoader: " + date.getClass().getClassLoader().getClass().getName());
            // 调用方法
            System.out.println(date.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印结果:


img_14c19a9e66b5c11e40705e0cc19137b0.png
图8.png
  • 如果loadClass方法的参数为类的全路径名
    // 加载类
    Class classDate = new MyClassLoader("class_temp").loadClass("com.mazaiting.ClassLoaderAttachment");
img_818c990831c85f051a4f230f3566e4d1.png
图9.png
  • \class_temp\路径下的ClassLoaderAttachment.class复制到\bin\com\mazaiting\路径下,然后运行
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file com/mazaiting/ClassLoaderAttachment

img_7f48349745186560caee3ec2d23e5787.png
图10.png

不合适的魔数错误(class文件的前六个字节是个魔数用来标识class文件的),因为加密后的数据AppClasssLoader无法解析。

目录
相关文章
|
2月前
|
Java 编译器 API
Java 密封类:精细化控制继承关系
Java 密封类:精细化控制继承关系
267 83
|
3月前
|
IDE Java 数据挖掘
Java 基础类从入门到精通实操指南
这份指南专注于**Java 17+**的新特性和基础类库的现代化用法,涵盖开发环境配置、数据类型增强(如文本块)、字符串与集合处理进阶、异常改进(如密封类)、IO操作及实战案例。通过具体代码示例,如CSV数据分析工具,帮助开发者掌握高效编程技巧。同时提供性能优化建议和常用第三方库推荐,适合从入门到精通的Java学习者。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
176 35
|
4月前
|
存储 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(7):不可变类设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中Java不可变类设计指南,废话不多说让我们直接开始。
72 0
|
6月前
|
Java 开发者
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
220 0
|
23天前
|
安全 IDE Java
Java记录类型(Record):简化数据载体类
Java记录类型(Record):简化数据载体类
122 0
|
2月前
|
Java API
Java API中Math类功能全景扫描
在实际使用时,这些方法的精确度和性能得到了良好的优化。当处理复杂数学运算或高精度计算时,`Math`类通常是足够的。然而,对于非常精细或特殊的数学运算,可能需要考虑使用 `java.math`包中的 `BigDecimal`类或其他专业的数学库。
84 11
|
2月前
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
169 4
|
2月前
|
Java API
深入解析Java API中Object类的功能
了解和合理运用 Object类的这些方法,对于编写可靠和高效的Java应用程序至关重要。它们构成了Java对象行为的基础,影响着对象的创建、识别、表达和并发控制。
59 0
|
2月前
|
安全 Java
JAVA:Collections类的shuffle()方法
`Collections.shuffle()` 是 Java 中用于随机打乱列表顺序的工具方法,适用于洗牌、抽奖等场景。该方法直接修改原列表,支持自定义随机数生成器以实现可重现的打乱顺序。使用时需注意其原地修改特性及非线程安全性。
93 0

热门文章

最新文章