【基础篇】四、类加载器ClassLoader

发布时间:2023年12月26日

1、类加载器

类加载器ClassLoader,是JVM提供给应用程序去获取类和接口的字节码数据的。

在这里插入图片描述

上面的类加载器对JVM进行了本地接口调用。本地接口即JNI,Java Native Interface,允许Java调用其他语言写的方法,如HostSpot类加载器调用JVM中用c++写的一些方法。

2、分类

两类:

  • Java代码中实现的:继承自抽象类ClassLoader
  • JVM底层源码实现的:用于加载程序运行时的基础类,如java.lang.String

在这里插入图片描述

使用Arthas来查看所有的类加载器:

//执行
classloader

除了上面提到的三种类加载器,DelegatingClassLoader是JDK底层用来提升反射效率的加载器

在这里插入图片描述

3、启动类加载器

启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C++编写的类加载器。 默认加载Java安装目录/jre/lib下的类文件,比如rt.jar(String、Integer、Date类都在这儿)、tools.jar、resources.jar

在这里插入图片描述

//尝试获取启动类的加载器
//相当于在Java程序(上层)中获取JVM(底层)的类加载器
public class BootstrapLoaderTest {

    public static void main(String[] args) throws IOException {
    
        ClassLoader classLoader = String.class.getClassLoader();
        
        System.out.println(classLoader);
    }
}
//返回null,从安全的角度考虑,这是合理的

在这里插入图片描述

继续用Arthas,sc指令查看某个类的详细信息

sc -d java.lang.String

发现这个基础类的加载器为空:

在这里插入图片描述

4、手动扩展启动类加载器

既然Java程序中获取不到启动类加载器,那如果后期我需要做扩展的jar包,如何让启动类加载器去加载我的jar呢:

  • 把自己的jar放入jre/lib下进行扩展:不推荐改JDK目录,放入也可能因为名称不会被正常加载
  • 使用参数扩展:-Xbootclasspath/a:jar包目录/jar包名,这里的a即add的含义

这里随便创建个模块,里面只有一个测试类MyLoad:

在这里插入图片描述

public class MyLoad {
    static {
        System.out.println("jar包中的Load类被加载了");
    }
}

mvn package打包后去另一个模块中添加JVM参数,

在这里插入图片描述

另一个模块中写个测试代码:

public class BootstrapLoaderTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Class<?> clazz = Class.forName("com.plat.domain.MyLoad");
        System.out.println(clazz);
        ClassLoader classLoader = clazz.getClassLoader();
        System.out.println(classLoader);
    }
}

运行,可以看到MyLoad类加载成功,且是被启动类加载器加载的:

在这里插入图片描述

以上的应用场景是:需要开发一些偏底层的类时,并需要用启动类加载器去加载它时,可考虑添加这个JVM参数,并把jar包拷贝到对应目录。如以镜像方式打包时,Dockerfile的cmd中可加ADD 操作,并添加JVM参数即可。

5、扩展类加载器

在这里插入图片描述

扩展类加载器负责加载那些通用,但不是很重要的类:Java安装目录下/jre/lib/ext下的类文件

在这里插入图片描述

比如上面的nashorn.jar,尝试获取这个jar中一个类ScriptEnvironment的类加载器:

public class LoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
    
        ClassLoader classLoader = ScriptEnvironment.class.getClassLoader();
        
        System.out.println(classLoader);
    }
}
//返回结果是扩展类加载器
sun.misc.Launcher$ExtClassLoader@1d44bcfa

如果以后的工作用写了一些通用,但不重要的类,可考虑让扩展类加载器去加载,扩展方式:

  • 放入/jre/lib/ext下进行扩展:不推荐去改JDK安装目录的内容
  • 使用参数:-Djava.ext.dirs=jar包目录
//-Djava.ext.dirs=jar包目录会覆盖原始的ext扩展目录
//正确用法为:
-Djava.ext.dirs=jar包目录;(windows的目录):(macos/linux的目录)

注意上面dirs路径中有空格时,运行报错,需要给dirs加上双引号,如:

-Djava.ext.dirs="C:\Program Files\jdk8\jre\lib\ext;D:\tmp\"

在这里插入图片描述

测试代码:

public class LoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
        //原有的
        ClassLoader classLoader = ScriptEnvironment.class.getClassLoader();
        System.out.println(classLoader);
        //让扩展类加载器去加载的jar
        Class<?> clazz = Class.forName("com.plat.domain.MyLoad");
        ClassLoader classLoader2 = clazz.getClassLoader();
        System.out.println(classLoader2);
    }
}

运行结果:

sun.misc.Launcher$ExtClassLoadera45ee12a7
sun.misc.Launcher$ExtClassLoader@45ee12a7

可以发现,JDK原来自己的ext目录下的类和我新加的jar包下的类,其类加载器都是扩展类加载器。保持了原扩展目录的同时,新加了自己的jar的类

6、应用程序类加载器

应用程序类加载器加载的是classPath下的类文件,包括项目中自己编写的类和接口的字节码文件,以及引入的第三方jar包中的类和接口的字节码文件

public class LoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
        //自己写的类
        ClassLoader classLoader1 = TestJvm.class.getClassLoader();
        System.out.println(classLoader1);
        //引入的jar的
        ClassLoader classLoader2 = FileUtil.class.getClassLoader();
        System.out.println(classLoader2);
    }
}

在这里插入图片描述

运行:

在这里插入图片描述

继续用Arthas工具,查看某个类加载器加载的路径全部打印出来

classloader –c hash值

在这里插入图片描述

文章来源:https://blog.csdn.net/llg___/article/details/135228200
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。