类加载器概念
类加载器是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
类加载器接收二进制数据,执行JNI(本地接口调用),在方法区和堆上创建对象。
类加载器只参与加载过程中字节码获取并加载到内存这一部分
类加载器分类
类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现。
虚拟机底层源码实现作用:保证Java程序运行中基础类被正确地加载,比如java.lang.String,确保可靠性。
Java代码类加载器:提供多种处理不同渠道的类加载器,也可以根据自己需求定制。所有Java中实现的类加载器都需要继承ClassLoader抽象类。
类加载器在jdk8及之前版本和jdk8之后版本的差别较大。
jdk8及之前版本的类加载器:
启动类加载器
是由Hotspot虚拟机提供的,使用C++编写的类加载器。默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar等。启动类加载器加载这些包进来之后相当于给程序提供基础的运行环境。
尝试在Java代码里获取启动类加载器
ClassLoader classLoader = String.class.getClassLoader();(String在rt.jar,获取String的类加载相当于获取启动类加载器)
最后打印出来的是null,为什么呢?
因为类加载器不存在于Java中,而在虚拟机中,我们上层应用直接获取底层虚拟机加载器信息会被认为是不安全操作。所以虚拟机不允许用代码获取启动类加载器。
扩展启动类加载器加载jar包
1.直接放入jre/lib包目录下进行扩展
不推荐,我们尽可能不要去更改JDK安装目录下的内容,会出现即使放进去,但由于文件名不匹配的问题,导致jar包无法被正常加载出来。
2.使用参数进行拓展
使用 -Xbootclasspath/a:jar包目录/jar包名 进行拓展。
首先我们对自己的类打一个jar包。
放进一个自己的目录里,并在新项目里添加虚拟机参数,填入自己的jar包路径。
之后便可以通过反射获取jar包的A。
使用场景:若是一些底层的基础类被很多类引用,则可以直接打包添加进启动类加载器。
拓展和应用类加载器
拓展类加载器和应用程序类加载器都是JDK提供的,使用JAVA编写的类加载器。
首先我们看拓展类加载器,是JDK提供的,使用Java编写的类加载器。默认加载Java安装目录/jre/lib/ext下的类文件,加载通用但没那么重要的jar包。
Java可以通过代码获取到它,例如:
自定义加载拓展类加载器的内容
1.放入/jre/lib/ext下进行拓展
不推荐,尽可能不要去更改JDK安装目录下的内容。
2.使用参数进行拓展
使用-Djava.ext.firs=jar包目录,进行拓展,但这个命令会覆盖原来的目录。需要先配置原来的目录,再配置自己的目录,用分号隔开各个目录。
例子:
应用程序类加载器
加载classPath下的类文件,包括自己写的和第三方的类文件。
例如:
//当前项目中创建的Student类
Student student = new Student();
ClassLoader classLoader = Student.class.getClassLoader();
//maven依赖中包含的类
ClassLoader classLoader1 = FileUtils.class.getClassLoader();
这两种情况都可以获取到应用类加载器。
jdk9及之后的类加载器
在jdk9之后,引入了module的概念,类加载器在设计上发生了很多变化。
1.类不再存放在jar包中,而是放在jmod文件中。
2.启动类加载器使用Java编写,位于jdk.internal.loader.ClassLoaders类中。
Java中的BootClassLoader继承自BuiltinClassLoader实现从Jmodes中找到要加载的字节码资源文件。
之前我们说过,Java启动类加载器由C++编写底层逻辑,Java代码无法进行获取,现在启动类加载器由Java编写后是不是能获取了呢?
答案是否定的,启动类加载器依然无法通过Java代码获取到,返回的仍然是null,保持了统一。
3.拓展类加载器被替换成了平台类加载器
平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多是为了与老版本的设计方案兼容,自身没有特殊的逻辑。因为模块化设计思想本身具有拓展性,而拓展类是为了在jdk核心类之外扩展一些类,理论上来说,采用模块化设计思想之后就不需要这个加载器了。