关于类加载的思考

近来看tomcat源码,莫名有一种畅快的感觉,解决了很多疑惑,也产生了新的疑惑

什么是 ClassLoader

Java 代码要想运行,首先需要将源代码进行编译生成 .class 文件,然后 JVM 加载 .class 字节码文件到内存,而 .class 文件是怎样被加载到 JVM 中的就是Java ClassLoader 要做的事情。
那么 .class 文件什么时候会被类加载器加载到 JVM 中那?

  • 比如执行 new 操作时候;
  • 当我们使用 Class.forName(“包路径+类名”)
    Class.forName(“包路径+类名”,ClassLoader)
    ClassLoader.loadClass(“包路径+类名”)的时候就触发了类加载器去类加载对应的路径去查找 *.class,并创建 Class 对象。
java 体系的类加载器
  • 启动类加载器(Bootstrap ClassLoader):加载对象是java的核心类库,把一些的 java 类加载到 jvm 中,它并不是我们熟悉的 ClassLoader,而是 jvm 层面由 C/C++ 实现的类加载器,负责加载 $JAVA_HOME/jre/lib 目录下 jvm 指定的类库,它是无法被 java 应用程序直接使用的

  • 扩展类加载器(Extension Classloader):它是一个 ClassLoader 实例,父加载器是启动类加载器,它负责加载 $JAVA_HOME/jre/lib/ext 目录的类库

  • 应用类加载器(Application ClassLoader):又叫做系统类加载器(System ClassLoader),负责加载用户类路径(-cp参数)指定的类库,可以通过 ClassLoader.getSystemClassLoader() 获取,它也是由启动类加载器加载的
    自定义类加载器:应用程序根据自己的需求开发的类加载器,可以继承 ClassLoader,当然也可以不继承

下图描述了类加载器的关系图,其中自定义类加载器有N多个

在这里插入图片描述

我们有必要了解下关于类加载有几个重要的知识点:

在 Java 中我们用完全类名来标识一个类,而在 JVM 层面,使用完全类名 + CloassLoader 对象实例 ID 作为唯一标识,因此使用不同实例的类加载器,加载的两个同名的类,他们的类实例是不同的,并且不能强制转换
在双亲委派机制中,类加载器查找类时,是一层层往父类加载器查找的,最后才查看自己,如果都找不到则会抛出异常,而不是一层层往下找的
每个运行中的线程都有一个 CloassLoader,并且会从父线程中继承(默认是应用类加载器),在没有显式声明由哪个类加载器加载类时(比如 new 关键字),将默认由当前线程的类加载器加载该类

tomcat 类加载器

根据实际的应用场景,我们来分析下 tomcat 类加载器需要解决的几个问题

  • 为了避免类冲突,每个 webapp 项目中各自使用的类库要有隔离机制
  • 不同 webapp 项目支持共享某些类库
  • 类加载器应该支持热插拔功能,比如对 jsp 的支持、webapp 的 reload 操作
    为了解决以上问题,tomcat设计了一套类加载器,如下图所示。在 Tomcat 里面最重要的是 Common 类加载器,它的父加载器是应用程序类加载器,负责加载 c a t a l i n a . b a s e / l i b 、 {catalina.base}/lib、 catalina.base/lib{catalina.home}/lib 目录下面所有的 .jar 文件和 .class 文件。下图的虚线部分,有 catalina 类加载器、share 类加载器,并且它们的 parent 是 common 类加载器,默认情况下被赋值为 Common 类加载器实例,即 Common 类加载器、catalina 类加载器、 share 类加载器都属于同一个实例。当然,如果我们通过修改 catalina.properties 文件的 server.loader 和 shared.loader 配置,从而指定其创建不同的类加载器
    在这里插入图片描述
学习过程中的疑惑
1. 系统,当前与线程上下文类加载器分别是指什么?

一般指的是应用类加载器,为啥是应用类加载器?只有类加载器才可以加载所有类的可能

2.tomcat 启动两个项目时,会启动几个JVM?

一个,在 JVM 层面,使用完全类名 + CloassLoader 对象实例 ID 作为唯一标识某个类,同一个jvm内,只要类加载的实例不同,就可以加载不同版本的相同类。

同一个jvm加载不同版本的全限定名相同的类
package com.example.classloadertest.verification;


import com.crossoverjie.concurrent.IFindUser;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;

/**
 * 同一个jvm加载不同版本的全限定名相同的类
 */
public final class Tool {


    public static void main(String args[]) {



        // Construct the class loader we will be using
        ClassLoader classLoaderVersion1 = null;
        ClassLoader classLoaderVersion2 = null;
         File parentFile =  new File("./");
        try {
            ArrayList<File> packedversion1 = new ArrayList<>();
            ArrayList<File> unpacked = new ArrayList<>();
            unpacked.add(new File(parentFile, "testclasses"));
            packedversion1.add(new File(parentFile, "testlibversion1"));

            classLoaderVersion1 = createClassLoader
                (unpacked.toArray(new File[0]),
                        packedversion1.toArray(new File[0]),
                 null);


            ArrayList<File> packedversion2 = new ArrayList<>();
            packedversion2.add(new File(parentFile, "testlibversion2"));


            classLoaderVersion2 = createClassLoader
                    (unpacked.toArray(new File[0]),
                            packedversion2.toArray(new File[0]),
                            null);


            Class<IFindUser>  ChinaNameVersion1Class   = (Class<IFindUser>) classLoaderVersion1.loadClass("com.crossoverjie.concurrent.ChinaName");
            Class<IFindUser>  ChinaNameVersion2Class   = (Class<IFindUser>) classLoaderVersion2.loadClass("com.crossoverjie.concurrent.ChinaName");

            IFindUser     version1Name  = ChinaNameVersion1Class.newInstance();
            IFindUser     version2Name  = ChinaNameVersion2Class.newInstance();


       System.out.println(version1Name.getClass()+"   "+version1Name.getName(""));
       System.out.println(version2Name.getClass()+"   "+version2Name.getName(""));
        } catch (Exception e) {
            e.printStackTrace();

        }


    }
    public static ClassLoader createClassLoader(File unpacked[],
                                                File packed[],
                                                final ClassLoader parent)
            throws Exception {


        // Construct the "class path" for this class loader
        Set<URL> set = new LinkedHashSet<>();

        // Add unpacked directories
        if (unpacked != null) {
            for (int i = 0; i < unpacked.length; i++)  {
                File file = unpacked[i];
              System.out.println(file.canRead()+file.getAbsolutePath());

                if (!file.canRead())
                    continue;
                file = new File(file.getCanonicalPath() + File.separator);
                URL url = file.toURI().toURL();

                set.add(url);
            }
        }

        // Add packed directory JAR files
        if (packed != null) {
            for (int i = 0; i < packed.length; i++) {
                File directory = packed[i];
                if (!directory.isDirectory() || !directory.canRead())
                    continue;
                String filenames[] = directory.list();
                if (filenames == null) {
                    continue;
                }
                for (int j = 0; j < filenames.length; j++) {
                    String filename = filenames[j].toLowerCase(Locale.ENGLISH);
                    if (!filename.endsWith(".jar"))
                        continue;
                    File file = new File(directory, filenames[j]);

                    URL url = file.toURI().toURL();
                    set.add(url);
                }
            }
        }

        // Construct the class loader itself
        final URL[] array = set.toArray(new URL[set.size()]);
        return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);
                        else
                            return new URLClassLoader(array, parent);
                    }
                });
    }



}





在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个用于构建Java企业级应用的开发框架,为开发者提供了自动配置、快速开发、便捷部署等功能。对于类加载器的使用,Spring Boot提供了默认的类加载器机制,但也可以通过自定义类加载器来实现一些特殊的需求。 自定义类加载器可以通过继承ClassLoader类来实现。在自定义类加载器中,我们可以重写findClass方法,在该方法中实现自己的类加载逻辑。例如,可以从特定的位置加载文件,或者从其他资源中加载类。通过自定义类加载器,我们可以灵活地加载一些不在常规位置的类文件或资源。 在Spring Boot中,可以使用自定义类加载器来实现一些插件化的功能。例如,可以实现一个插件类加载器,负责加载插件模块,并将其实例化为Spring Bean。这样,在运行时我们可以动态地加载并使用一些自定义的功能模块,而不需要在编译时就将其包含在应用程序中。 另外,自定义类加载器还可以用于热部署功能的实现。通过定时或者其他方式,我们可以在运行时重新加载某些类文件,以实现代码的热更新,避免了重启应用程序的操作。 需要注意的是,使用自定义类加载器需要谨慎。不正确的使用或者滥用类加载器可能导致类冲突、内存泄漏等问题。因此,在自定义类加载器时,需要仔细考虑设计和实现,确保安全性和稳定性。 总之,Spring Boot提供了默认的类加载器机制,但也支持自定义类加载器。通过自定义类加载器,我们可以实现一些特殊的需求,例如插件化功能和热部署功能。但在使用自定义类加载器时,需要谨慎思考和设计,确保安全性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值