深入探究Java类加载机制

一、概述

Java虚拟机(JVM)是运行Java程序的重要平台。在JVM中,类的加载、连接、初始化是Java程序运行的基础。在Java中,类是按需加载的,也就是在程序首次使用类的时候才会被加载。Java采用的是双亲委派模型的类加载机制,这个机制对于Java的生态系统是非常重要的。本文将从多个方面阐述Java类加载机制。

二、类的生命周期

在了解Java的类加载机制之前,需要了解类的生命周期:

  1. 加载:从文件系统、网络等来源读取二进制数据,并且创建出Class对象。
  2. 连接:包括验证、准备和解析三个阶段。
  3. 初始化:为类的静态变量赋初值,执行<clinit>方法。
  4. 使用:类被调用。
  5. 卸载:类不再被引用,被GC回收。

三、类加载机制

1. 双亲委派模型

Java采用的是双亲委派模型的类加载机制,这个机制对Java的生态系统是非常重要的。它是从Java1.2开始加入的,严格来说应该叫做双亲委派委派模型。简单来说,就是当一个类加载器接收到一个类加载请求后,它会将请求转发给它的父类加载器,直到最顶层的启动类加载器。

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先从缓存中查找已经被加载过的类
            Class<?> c = findLoadedClass(name);
            // 如果没有被加载,再让父亲去试一下
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException ignored) {}
                // 如果父亲都找不到,最后就由自己去加载
                if (c == null) {
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

2. 双亲委派的好处

借助于双亲委派机制,Java程序可以避免类的重复加载。这种机制从根本上保证了Java程序的稳定性和安全性。例如,在使用微服务和大型框架的情况下,如果每个服务请求使用不同的类加载器,就会造成类重复加载,从而会导致内存使用量过高、版本不一致等问题。通过引入双亲委派机制,可以将类加载的责任传递给父类加载器,避免了类重复加载问题。

3. 破坏双亲委派模型

虽然双亲委派模型时Java的默认类加载机制,但是它并不是万能的。有时候,我们会遇到一些特殊场景,需要打破这个机制。Java提供了两种方式来打破双亲委派机制:

  1. 线程上下文类加载器
  2. 为了解决上述问题,Java引入了线程上下文类加载器的概念。它是从Java1.2引入的。当默认的类加载器无法满足某个类加载请求时,可以通过线程上下文类加载器来加载。可以通过Thread.currentThread().setContextClassLoader()方法来设置线程上下文类加载器。

        public static void main(String[] args) {
            Thread.currentThread().setContextClassLoader(new MyClassLoader());
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Class<?> clazz = classLoader.loadClass("com.example.MyClass");
        }
    
  3. 自定义类加载器
  4. 通过自定义类加载器,可以打破双亲委派模型,从而实现更灵活的类加载机制。自定义类加载器需要继承java.lang.ClassLoader类,并重写其中的findClass()方法。调用自定义类加载器的代码应该在类和类加载器分离的情况下,通过newInstance()来创建新的类对象。

        public class MyClassLoader extends ClassLoader {
            @Override
            protected Class<?> findClass(String name) {
                // 从指定的磁盘路径加载类
                byte[] bytes = loadClassBytes(name);
                return defineClass(name, bytes, 0, bytes.length);
            }
        }
    

四、常见问题

1. 类被重复加载了怎么办?

我们可以通过JVM参数-verbose:class来查看类的加载情况。如果出现类重复加载的情况,可以使用强制主动加载的方式来防止重复加载。例如,在使用spring-boot-devtools的场景下,可以在build.gradle文件中添加以下代码:

plugins {
    id 'org.springframework.boot' version '2.3.1.RELEASE' apply false
}
 
ext['spring-boot'] = ["org.springframework.boot:spring-boot-devtools"]
dependencies {
    runtimeOnly "${spring-boot}"
}
 
configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
}
 
task developmentJar(type: Jar) {
    classifier = 'development'
    from sourceSets.main.output
    appendix = "-${version}-SNAPSHOT"
}
 
artifacts {
    developmentOnly developmentJar
}
 
bootJar {
    requiresUnpack '**/spring-boot-devtools-*.jar'
}
 
task devtools(additionalResourceDirs: sourceSets.main.output.resourcesDir) {
    doLast {
        files(additionalResourceDirs).visit {
            def file = it.getFile();
            if (file.path.endsWith('.class')) {
                Class.forName(file.path.split('main\\\\classes\\\\', 2)[1].replaceAll('\\\\', '.').replaceAll('.class', ''))
            }
        }
    }
}
 
tasks.compileJava {
    finalizedBy devtools
}

2. 打破双亲委派模型会带来什么问题?

在某些情况下,打破双亲委派机制可能会导致类重复加载、版本不一致等问题。例如:Maven的classpath中存在多个版本的同一依赖包时,使用默认的双亲委派模型无法加载到正确版本的类,这时就可以通过线程上下文类加载器来加载。但需要注意的是,过多地使用自定义类加载器会降低Java程序的性能。

五、总结

Java的类加载机制是Java程序运行的基础,其中双亲委派模型是其重要的机制之一。通过对双亲委派模型的理解和应用,可以保证Java程序的稳定性和安全性。当然,在必要的情况下,也可以通过线程上下文类加载器或者自定义类加载器来打破默认的类加载机制,实现更灵活的类加载方式。

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/156901.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝的头像小蓝
上一篇 2024-11-18 01:58
下一篇 2024-11-18 01:58

相关推荐

  • java client.getacsresponse 编译报错解决方法

    java client.getacsresponse 编译报错是Java编程过程中常见的错误,常见的原因是代码的语法错误、类库依赖问题和编译环境的配置问题。下面将从多个方面进行分析…

    编程 2025-04-29
  • Java JsonPath 效率优化指南

    本篇文章将深入探讨Java JsonPath的效率问题,并提供一些优化方案。 一、JsonPath 简介 JsonPath是一个可用于从JSON数据中获取信息的库。它提供了一种DS…

    编程 2025-04-29
  • Java腾讯云音视频对接

    本文旨在从多个方面详细阐述Java腾讯云音视频对接,提供完整的代码示例。 一、腾讯云音视频介绍 腾讯云音视频服务(Cloud Tencent Real-Time Communica…

    编程 2025-04-29
  • QML 动态加载实践

    探讨 QML 框架下动态加载实现的方法和技巧。 一、实现动态加载的方法 QML 支持从 JavaScript 中动态指定需要加载的 QML 组件,并放置到运行时指定的位置。这种技术…

    编程 2025-04-29
  • Java Bean加载过程

    Java Bean加载过程涉及到类加载器、反射机制和Java虚拟机的执行过程。在本文中,将从这三个方面详细阐述Java Bean加载的过程。 一、类加载器 类加载器是Java虚拟机…

    编程 2025-04-29
  • Java Milvus SearchParam withoutFields用法介绍

    本文将详细介绍Java Milvus SearchParam withoutFields的相关知识和用法。 一、什么是Java Milvus SearchParam without…

    编程 2025-04-29
  • Java 8中某一周的周一

    Java 8是Java语言中的一个版本,于2014年3月18日发布。本文将从多个方面对Java 8中某一周的周一进行详细的阐述。 一、数组处理 Java 8新特性之一是Stream…

    编程 2025-04-29
  • Java判断字符串是否存在多个

    本文将从以下几个方面详细阐述如何使用Java判断一个字符串中是否存在多个指定字符: 一、字符串遍历 字符串是Java编程中非常重要的一种数据类型。要判断字符串中是否存在多个指定字符…

    编程 2025-04-29
  • VSCode为什么无法运行Java

    解答:VSCode无法运行Java是因为默认情况下,VSCode并没有集成Java运行环境,需要手动添加Java运行环境或安装相关插件才能实现Java代码的编写、调试和运行。 一、…

    编程 2025-04-29
  • Java任务下发回滚系统的设计与实现

    本文将介绍一个Java任务下发回滚系统的设计与实现。该系统可以用于执行复杂的任务,包括可回滚的任务,及时恢复任务失败前的状态。系统使用Java语言进行开发,可以支持多种类型的任务。…

    编程 2025-04-29

发表回复

登录后才能评论