浅谈ClassLoader
本篇为学习JAVA虚拟机的第二篇文章,上一篇文章初步提到了class文件,以及一个最简单程序执行的指令含义,我们提到,是由JAVA虚拟机先加载这些编译好的class文件,然后再去根据解析出来的指令去转换为具体平台上的机器指令执行,但是加载这个class文件时如何加载的呢?其实就涉及比较重要的东西:ClassLoader
有一个基本认识,从编译到实例化对象的过程可以概括为以下三个阶段:
- 编译器将
xxx.java
源文件编译为xxx.class
字节码文件 ClassLoader
将字节码转换为JVM种的Class<xxx>
对象- JVM利用
Class<xxx>
对象实例化为xxx
对象
一、JVM系统结构
ClassLoader
:依据特定格式,加载class文件到内存Execution Engine
:对命令进行解析Native Interface
:融合不同开发语言的原生库为Java所用Runtime Data Area
:JVM内存空间结构模型
首先通过ClassLoader
加载符合条件的字节码文件到内存中,然后通过Execution Engine
解析字节码指令,交由操作系统去执行。
二、什么是ClassLoader
ClassLoader
在java中有着非常重要的作用,它主要工作在Class
装载的加载阶段,其主要作用是从系统外部获得Class
二进制数据流。他是JAVA的核心组件,所有的Class
都是由ClassLoader
进行加载的,ClassLoader
负责通过将Class
文件里的二进制数据流装载进系统,然后交给JAVA虚拟机进行连接、初始化等操作。
简而言之,就是加载字节码文件。
我们翻开ClassLoader
源码看看:
1 | public abstract class ClassLoader {...} |
它是一个抽象类,下面我们再来说具体的实现类。
里面比较重要的是loadClass()
方法:
1 | public Class<?> loadClass(String name) throws ClassNotFoundException { |
就是根据name
来加载字节码文件,返回Class
实例,加载不到则抛出ClassNotFoundException
异常。
三、ClassLoader的种类
- 启动类加载器(
Bootstrap ClassLoader
):由C++
语言实现(针对HotSpot
),加载核心库java.*
。
- 扩展类加载器(
Extension ClassLoader
):Java编写,加载扩展库javax.*
它扫描的是哪个路径呢?
我们看到,它负责将 <JAVA_HOME >/lib/ext
或者由系统变量-Djava.ext.dir
指定位置中的类库 加载到内存中。
- 应用程序类加载器(
Application ClassLoader
):Java编写,加载程序所在目录
它负责将 用户类路径(java -classpath
或-Djava.class.path
变量所指的目录,即当前类所在路径及其引用的第三方类库的路径,看截图的最后一行,显示的是当前项目路径。
- 自定义
ClassLoader
:自定义
四、如何自定义ClassLoader
要自己实现一个ClassLoader
,其核心涉及两个方法:
1 | protected Class<?> findClass(String name) throws ClassNotFoundException { |
首先想一下为什么是这两个类?
其实答案在loadClass()
这个方法里面。如果已经熟悉双亲委派模型的同学,都会知道加载Class
对象是先委派给父亲,看父亲是否已经加载,如果没有加载过,则从最顶层父亲开始逐层往下进行加载,这一块详细在下一篇文章中解释,我们先走马观花看看这个的核心方法长啥样:
1 | protected Class<?> loadClass(String name, boolean resolve) |
如果我们不去重写findClass(name)
方法,默认是直接抛出找不到的异常,所以我们要对这个方法进行重写。
由于字节码文件是一堆二进制流,所以需要一个方法来根据这个二进制流来定义成一个类,即defineClass()
这个方法来实现这个功能。
说的比较抽象,下面来真正实践一把!
五、实践自定义ClassLoader
首先写一个类:Robot.java
1 | public class Robot { |
在对Robot.java
用javac
编译之后形成Robot.class
文件,就要删除本项目下的这个Robot.java
文件,要不然就会被AppClassLoader
类加载先加载了,而无法再被我们的自定义类加载器再去加载。这个Robot.class
文件我就直接放到桌面去了。路径为C:/Users/swg/Desktop/
.
然后定义一个自定义的ClassLoader
,按照上面的理论,只要重写findClass
就可以指定到某个地方获取class字节码文件,此时获取的是二进制流文件,转换为字节数组,最后借用defineClass
获取真正的Class
对象。
1 | public class MyClassLoader extends ClassLoader{ |
最后测试一下能不能用自定义类加载器去加载到Robot
对应的Class对象:
1 | public class Test { |
打印结果:
1 | MyClassLoader@677327b6 |
好了,学习了关于ClassLoader
的分类以及如何自定义ClassLoader
,我们知道了类加载器的基本实现,上面谈到了一个重要方法是loadClass
,这就涉及了类加载器的双亲委派模型。下一节从代码层面好好来说说这个,其实很简单。