ClassLoader 从精通到陌生

类加载器

类加载器用于实现类的加载动作,负责读取java字节码,并将其转化为Class实例
在这里插入图片描述

类与类加载器的关系

任意一个类和加载它的类加载器一同确立其在JVM中的唯一性,并且每一个类加载器都有一个独立的类名称空间

比较两个类是否相等,前提是在同一个类加载器加载的前提下

public class ClassLoadTest {

    public static void main(String[] args) throws Exception{
        ClassLoader classLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] bytes = new byte[is.available()];
                    is.read(bytes);
                    return defineClass(name, bytes, 0, bytes.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };


            Object o = classLoader.loadClass("JVM.Class.classload.ClassLoadTest").newInstance();
            System.out.println(o.getClass());
            System.out.println(o instanceof JVM.Class.classload.ClassLoadTest);
    }
}

结果

class JVM.Class.classload.ClassLoadTest
false
加载器分类

请添加图片描述

JVM角度
  • 启动类加载器 Bootstrap ClassLoader

    由C++实现

  • 其他加载器

​ 独立于虚拟机外部,都继承自抽象类ClassLoader

开发人员角度
  • 启动类加载器 Bootstrap ClassLoader

    Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载,比如String.class就是rt包下的)。

    启动类加载器是无法被Java程序直接引用的,所以String.class.getClassLoader为null,同理如果一个类的classLoader为null那它就是被Bootstrap ClassLoader加载

    简单来说就是加载 jdk的核心类库

  • 拓展加载器 Extension ClassLoader

    Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JDK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

  • 应用程序加载器 Application ClassLoader

    也称系统类加载器(System ClassLoader)ClassLoader.getSystemClassLoader() 方法的默认返回值

    Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(这里的应用程序路径,便是指虚拟机参数 -cp/-classpath、系统变量 java.class.path 或环境变量 CLASSPATH 所指定的路径。)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

类加载器之间的关系
双亲委派模型

如果一个类收到类加载器的请求,它首先不会自己去尝试加载这个类,而是让父加载器去完成,依次向上,只有当父加载器在它搜索范围中没有找到所需的类时,即无法完成,子加载器才会尝试去加载该类

在这里插入图片描述

这种层级关系称为类加载器的双亲委派模型

双亲委派代码实现

public Class<?> loadClass(String name)throws ClassNotFoundException {
            return loadClass(name, false);
    }
    protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
            // 首先判断该类型是否已经被加载
            Class c = findLoadedClass(name);
            if (c == null) {
                //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
                try {
                    if (parent != null) {
                         //如果存在父类加载器,就委派给父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                    //如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
                        c = findBootstrapClass0(name);
                    }
                } catch (ClassNotFoundException e) {
                 // 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
                    c = findClass(name);
                }
            }
        //是否需要加载时去解析
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
  
双亲委派的好处

同一个类只会被加载一次,保证java程序安全稳定的运行

查看各个加载器所以加载的东西

    public static void main(String[] args) {

        System.out.println("启动类加载器 Bootstrap ClassLoader");
        Arrays.stream(Launcher.getBootstrapClassPath().getURLs()).forEach(url->{
            System.out.println(url);
        });
        URLClassLoader systemClassLoader = (URLClassLoader)                         ClassLoader.getSystemClassLoader();
        URLClassLoader extClassLoader = (URLClassLoader)systemClassLoader.getParent();
        System.out.println("拓展加载器 Extension ClassLoader");
        Arrays.stream(extClassLoader.getURLs()).forEach(url->{
            System.out.println(url);
        });

        System.out.println("应用程序加载器 Application ClassLoader");
        Arrays.stream(systemClassLoader.getURLs()).forEach(url->{
            System.out.println(url);
        });

    }

输出:

启动类加载器 Bootstrap ClassLoader
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/resources.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/rt.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/sunrsasign.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/jsse.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/jce.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/charsets.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/jfr.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/classes
拓展加载器 Extension ClassLoader
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/access-bridge-32.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/dnsns.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/jaccess.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/jfxrt.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/localedata.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/nashorn.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/sunec.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/sunmscapi.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/sunpkcs11.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/zipfs.jar
应用程序加载器 Application ClassLoader
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/charsets.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/deploy.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/access-bridge-32.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/dnsns.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/jaccess.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/jfxrt.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/localedata.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/nashorn.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/sunec.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/sunmscapi.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/sunpkcs11.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/ext/zipfs.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/javaws.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/jce.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/jfr.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/jfxswt.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/jsse.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/management-agent.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/plugin.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/resources.jar
file:/C:/Program%20Files%20(x86)/Java/jdk1.8.0_202/jre/lib/rt.jar
file:/D:/study/ClassLoadTest/target/classes/
file:/D:/software/IntelliJ%20IDEA%202021.3/lib/idea_rt.jar
使用场景

我们应该何时使用类加载器呢?

1.隔离性

从Tcomcat来分析,为什么Tomcat打破双亲委派?

Tomcat容器允许同时运行多个Web程序,每个程序依赖的类的相互隔离的,如果采用双亲委派,A程序依赖hutool v1.0,B程序依赖hutool v2.0。当A程序加载完后,B程序的hutool2.0 将得不到加载。

所以Tomcat的Web程序都有属于自己的ClassLoader

2.热部署

通过Classloader可以实现动态替换类

一个简单的热部署梨子;

public interface AService {
    String hello();
}
public class AServiceImpl implements AService {

    @Override
    public String hello() {
        return "v1111";
    }
}

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.thread.ThreadUtil;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @program: ClassLoadTest
 * @description:
 * @author: TheBlindM
 * @create: 2021-12-04 22:05
 **/

public class HotClassLoadTest {


    public static void main(String[] args) {
        ThreadUtil.execute(new Monitor());

        while (true) {
            AService aService = ServiceFactory.getAService();
            System.out.println(aService.hello());
            ThreadUtil.sleep(2000);
        }


    }


    static class ServiceFactory {
        static volatile AService aService;

        public static AService getAService() {
            if (Objects.nonNull(aService)) {
                return aService;
            }
            return aService = createAService();
        }

        private static AService createAService() {

            HotClassLoad hotClassLoad = new HotClassLoad();
            try {
                Class<?> aClass = hotClassLoad.findClass("AServiceImpl");
                if (Objects.nonNull(aClass)) {
                    return (AService) aClass.newInstance();
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }

        public static void reloadAService() {
            aService = createAService();
        }


    }


    static class Monitor implements Runnable {
        static final Map<String, Long> LAST_MODIFIEDS = new HashMap<>();

        @Override
        public void run() {
            while (true) {
                File resFile = FileUtil.file(this.getClass().getResource("").getPath());
                Arrays.stream(resFile.listFiles()).forEach(file -> {
                    String key=file.getPath();
                    long lastModified = file.lastModified();
                    Long oldLastModified = LAST_MODIFIEDS.get(key);
                    if(Objects.isNull(oldLastModified)){
                        LAST_MODIFIEDS.put(file.getPath(),lastModified);
                        return;
                    }
                    if(!oldLastModified.equals(lastModified)){
                        LAST_MODIFIEDS.put(key,lastModified);
                        ServiceFactory.reloadAService();
                    }
                });
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }

    }

    static class HotClassLoad extends ClassLoader {

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            String path = name.replaceAll("\\.", File.separator) + ".class";
            // 获取类的class文件字节数组
            byte[] classData = FileUtil.readBytes(path);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                //直接生成class对象
                return defineClass(name, classData, 0, classData.length);
            }
        }
    }


}

类加载方式
  • JVM初始化加载
  • 通过Class.forName()方法动态加载
  • ClassLoader.loadClass()方法动态加载

ClassLoader.loadClass()与Class.forName()的区别

Class.forName( ):加载Class文件到jvm后,并且初始化类(比如static代码块)

ClassLoader.loadClass( ): 只加载到jvm,不会初始化类

参考:
深入理解jvm虚拟机

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值