类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术
类加载器会通过二进制流的方式获取到字节码文件的内容,接下来将获取到的数据交给Java虚拟机,虚拟机会在方法区和堆上生成对应的对象保存字节码信息。
类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现的。
虚拟机底层实现
:源代码位于Java虚拟机的源码中,实现语言与虚拟机底层语言一致,比如Hotspot使用C++。主要目的是保证Java程序运行的基础类被正确地加载,比如java.lang.String
,Java虚拟机需要确保其可靠性。JDK中默认提供或者自定义
:JDK中默认提供了多种处理不同渠道的类加载器,程序员也可以自己根据需求使用Java语言定制。所有Java中实现的类加载器都需要继承ClassLoader这个抽象类。可以查看所有类加载器,即ClassLoader的子类
类加载器的设计,JDK8和8之后的版本差别较大。首先来看JDK8及之前的版本,这些版本中默认的类加载器有如下几种:
启动类加载器(Bootstrap ClassLoader、C++实现)
:加载核心类,String类扩展类加载器(Extension ClassLoader、Java实现)
:加载扩展类,拓展Java中比较通用的类,只是通用,不是特别重要,最重要的在启动类加载器加载了应用程序类加载器(Application ClassLoader、Java实现)
:加载应用classpath中的类,包括我们自己写的类,还有第三方Jar包的类自定义类加载器(Java实现)
:需要重写findClass方法。类加载器的详细信息可以通过Arthas的classloader命令查看
classloader
- 查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource
运行如下代码:
/**
* 启动程序类加载器案例
*/
public class BootstrapClassLoaderDemo {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader);
System.in.read();
}
}
这段代码通过String类获取到它的类加载器并且打印,本来以为是Bootstrap ClassLoader
,结果是null
。这是因为启动类加载器在JDK8中是由C++语言来编写的,在Java代码中去获取既不适合也不安全,所以才返回null
在Arthas中可以通过sc -d 类名
的方式查看加载这个类的类加载器详细的信息,比如:
通过上图可以看到,java.lang.String类的类加载器是空的,Hash值也是null。所以只要看到为null,就知道这是启动类加载器
如果用户想扩展一些比较基础的jar包,让启动类加载器加载,有两种途径:
-Xbootclasspath/a:jar包目录/jar包名
进行扩展,参数中的/a代表新增。【使用参数进行扩展实现流程】
先创建第一个项目,打包成jar包
再创建第二个项目,在第二个项目的IDEA配置中添加虚拟机参数,就可以加载D:/jvm/jar/classloader-test.jar
这个jar包了。
使用Class.forName
获取Jar包的类,可以正常执行初始化,说明自己拓展的Jar包被加载了
继承关系图如下:
ClassLoader类
:定义了具体的行为模式,简单来说就是先从本地或者网络获得字节码信息,然后调用虚拟机底层的方法创建方法区和堆上的对象。这样的好处就是让子类只需要去实现如何获取字节码信息这部分代码。SecureClassLoader
:提供了证书机制,提升了安全性。URLClassLoader
:提供了根据URL获取目录下或者指定jar包进行加载,获取字节码的数据的能力。扩展类加载器和应用程序类加载器继承自URLClassLoader,获得了上述的三种能力。
扩展类加载器(Extension Class Loader)是JDK中提供的、使用Java编写的类加载器。默认加载Java安装目录/jre/lib/ext下的类文件。
如下代码会打印ScriptEnvironment类的类加载器。ScriptEnvironment是nashorn框架中用来运行javascript语言代码的环境类,他位于nashorn.jar包中被扩展类加载器加载。这些类我们很少用,所以被放到了扩展类加载器中。
/**
* 扩展类加载器
*/
public class ExtClassLoaderDemo {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = ScriptEnvironment.class.getClassLoader();
System.out.println(classLoader);
}
}
打印结果如下:
-Djava.ext.dirs=jar包目录
进行扩展,这种方式会覆盖掉原始目录,可以追加上原始目录,并使用 ;(windows系统所用符号) :(macos/linux) 进行分隔如下图中:
使用引号
将整个地址包裹起来,这样路径中即便是有空格也不需要当做特殊字符额外处理。路径中要包含原来ext文件夹,同时在最后加上扩展的路径。
应用程序类加载器会加载classpath下的类文件,默认加载的是项目中的类以及通过maven引入的第三方jar包中的类。
如下案例中,打印出Student
(自己写的)和FileUtils
(引入的)的类加载器:
/**
* 应用程序类加载器案例
*/
public class AppClassLoaderDemo {
public static void main(String[] args) throws IOException, InterruptedException {
//当前项目中创建的Student类
Student student = new Student();
ClassLoader classLoader = Student.class.getClassLoader();
System.out.println(classLoader);
//maven依赖中包含的类
ClassLoader classLoader1 = FileUtils.class.getClassLoader();
System.out.println(classLoader1);
Thread.sleep(1000);
System.in.read();
}
}
输出结果如下,这两个类均由应用程序类加载器加载。
类加载器的加载路径可以通过classloader –c hash值 查看:
查看应用程序类加载器所加载的jar包
该文章是本人学习 黑马程序员 的学习笔记,文章中大部分内容来源于 黑马程序员 的视频黑马程序员JVM虚拟机入门到实战全套视频教程,java大厂面试必会的jvm一套搞定(丰富的实战案例及最热面试题),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对 黑马程序员 的优质课程表示感谢。