JVM支持两种类型的类加载器,分别是启动类加载器(引导类加载器,Bootstrap ClassLoader)和自定义加载器(User-Defined ClassLoader)。如下图
从概念上来讲,自定义类加载器一般是指程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载都划分为自定义类加载器。(Extension ClassLoader系统类加载器 、系统类加载器System ClassLoader间接继承了Class Loader,也属于自定义加载器)
sun.misc.Launcher是一个Java虚拟机的入口应用,打开Launcher.class文件可以看到如下关系:
代码验证示例,如下:
class SonOfClassLoader extends ClassLoaderDemo{ }
public class ClassLoaderDemo {
public static void main(String[] args) {
//获取系统类加载器
ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader();
System.out.println("获取系统类加载器:"+systemClassLoader);
//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取其上层得到扩展类加载器
ClassLoader extClassLoader=systemClassLoader.getParent();
System.out.println("获取系统类加载器的上层:"+extClassLoader);
//sun.misc.Launcher$ExtClassLoader@677327b6
//再获取其上层:获取不到引导类加载器
ClassLoader bootstrapClassLoader=extClassLoader.getParent();
System.out.println("获取系统类加载器的上上层:"+bootstrapClassLoader);
//null
//对于用户自定义类来说:默认使用系统类加载器进行加载,所以可以看到地址都为@18b4aac2
System.out.println("用户自定义类的类加载器:"+ClassLoaderDemo.class.getClassLoader());
//sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader sonClassLoader= SonOfClassLoader.class.getClassLoader();
System.out.println("用户自定类子类的类加载器:"+sonClassLoader);
//sun.misc.Launcher$AppClassLoader@18b4aac2,验证了第一张图
System.out.println("用户自定类子类的父类类加载器:"+sonClassLoader.getParent());
//sun.misc.Launcher$ExtClassLoader@677327b6,验证了第一张图
//可见String类使用引导类加载器进行加载。--》Java的核心类库都是使用引导类加载器进行加载
System.out.println("java.lang.String类的类加载器:"+String.class.getClassLoader());
//null
System.out.println("java.lang.ClassLoader类的类加载器:"+ClassLoader.class.getClassLoader());
//null
}
}
运行结果:
获取系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
获取系统类加载器的上层:sun.misc.Launcher$ExtClassLoader@677327b6
获取系统类加载器的上上层:null
用户自定义类的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
用户自定类子类的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
用户自定类子类的父类类加载器:sun.misc.Launcher$ExtClassLoader@677327b6
java.lang.String类的类加载器:null
java.lang.ClassLoader类的类加载器:null
JVM自带的加载器
-
启动类加载器(引导类加载器,Bootstrap ClassLoader)
这个类加载器使用C/C++语言实现的,嵌套在JVM内部;
它用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar;resource.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类;
不继承自java.lang.ClassLoader,没有父加载器;
加载扩展类(ExtClassLoader)和应用扩展类加载器(AppClassLoader),并指定为它们的父类加载器;
出于安全考虑,Bootstrap ClassLoader(启动类加载器)只加载包名为java,javax,sum等开头的类;
验证代码:
import sun.misc.Launcher;
import java.net.URL;
public class BootstrapClassLoaderDemo {
public static void main(String[] args) {
System.out.println("获取启动类加载器能够加载的api路径:");
URL[] urls= Launcher.getBootstrapClassPath().getURLs();
for (URL element:urls){
System.out.println(element.toExternalForm());
}
}
}
运行结果:
获取启动类加载器能够加载的api路径:
file:/D:/JDK1.8/jre/lib/resources.jar
file:/D:/JDK1.8/jre/lib/rt.jar
file:/D:/JDK1.8/jre/lib/sunrsasign.jar
file:/D:/JDK1.8/jre/lib/jsse.jar
file:/D:/JDK1.8/jre/lib/jce.jar
file:/D:/JDK1.8/jre/lib/charsets.jar
file:/D:/JDK1.8/jre/lib/jfr.jar
file:/D:/JDK1.8/jre/classes
-
扩展类加载器(Extension ClassLoader)
java语言编写,由sum.misc.Launcher$ExtClassLoader实现;
派生于ClassLoader类;
父类加载器为启动类加载器(引导类加载器,Bootstrap ClassLoader);
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)
下加载类库.如果用户创建的jar放在此目录,也会自动由扩展类加载器加载;
public class ExtensionClassLoaderDemo {
public static void main(String[] args) {
System.out.println("获取扩展类加载器能够加载的api路径:");
// 获取java.ext.dirs的系统属性
String extDirs=System.getProperty("java.ext.dirs");
for (String element:extDirs.split(";")){
System.out.println(element);
}
}
}
运行结果:
获取扩展类加载器能够加载的api路径:
D:\JDK1.8\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
-
应用程序类加载器(系统类加载器,AppClassLoader)
java语言编写,由sum.misc.Launcher$AppClassLoader实现;
派生于ClassLoader类;
父类加载器为扩展类加载器;
它负责加载环境变量classpath或者系统属性:java.class.path指定路径下的类库;
该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载;
通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器;
-
用户自定义加载器(User-Defined ClassLoader)
在java的日常开发应用程序中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定值类的加载方式.
为什么需要自定义类加载器?
隔离加载类;
修改类加载的方式;
扩展加载源;
防止源码泄露;(预防反编译篡改等,需要加密,需要自己定义类加载器来加载)
用户自定义类加载器实现步骤:
-
开发人员可以通过继承抽象类java.lang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊需求;
-
在JDK1.2之前,在自定义类加载器时,总会去继承Class Loader类并重写LoadClass()方法,从而实现自定义的类加载器,但在1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中;
-
在编辑自定义类加载器时,如果没有太过于复杂的需求,而可以直接继承URLClassLoader类,这样就可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁;
实现自定义类加载器的代码框架,如下:
import java.io.FileNotFoundException;
/**
* @ClassName CustomClassLoader
* @Description 自定义用户类加载器
* @author taemin
* @date 2020-05-02 12:18
*/
public class CustomClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
try {
byte[] result=getClassFromCustomPath(name);
if (result==null){
throw new FileNotFoundException();
}else {
return defineClass(name,result,0,result.length);
}
}catch (FileNotFoundException e){
e.printStackTrace();
}
throw new ClassNotFoundException(name);
}
private byte[] getClassFromCustomPath(String name) {
//自定义路径中加载指定类:细节略
//如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作
return null;
}
public static void main(String[] args) {
CustomClassLoader customClassLoader=new CustomClassLoader();
try{
Class<?> clazz=Class.forName("One",true,customClassLoader);
Object obj=clazz.newInstance();
System.out.println(obj.getClass().getClassLoader());
}catch (Exception e){
e.printStackTrace();
}
}
}
获取ClassLoader的途径:
-
获取当前类的ClassLoader
如:类名.class.getClassLoader()
-
获取当前线程上下文的ClassLoader
如:Thread.currentThread().getContextClassLoader()
-
获取系统的ClassLoader
ClassLoader.getSystemClassLoader()
-
获取调用者的CLassLoader