类加载器是什么?
Java类加载器(Java Classloader)负责动态地将Java类加载到Java虚拟机的内存空间内, 是Java运行时环境(Java Runtime Environment)的一部分,JVM默认有3个类加载器,每个类加载器负责加载特定位置的Java类。
一、Bootstrap ClassLoader(引导类加载器)
该类加载器通常由C++语言实现,不继承任何Java类,负责加载System.getProperty(“sun.boot.class.path”)所指定核心Java库,也可以通过java -Xbootclasspath指定其搜索路径。
public class Test {
public static void main(String[] args) {
String path = System.getProperty("sun.boot.class.path");
String[] paths = path.split(";");
for (String string : paths) {
System.out.println(string);
}
}
}
执行结果:
可见引导类加载器所指定的java库是在D:\Program_Files\Java\jre1.8.0_131\lib目录下,并且可以在D:\Program_Files\Java\jre1.8.0_131目录下创建classe文件夹,在里面存放需要被引导类加载器所加载的java文件。
注意:有的电脑在安装jdk时没有再次安装jre,而jdk中自带jre,所以没有再次安装jre的电脑输出路径为D:\Program_Files\Java\jdk1.8.0_131\lib目录
二、ExtClassLoader(扩展类加载器)
该类加载器由sun.misc.Launcher$ExtClassLoader类实现,
负责加载System.getProperty(“java.ext.dirs”)所指定的Java的扩展库,也可以通过java -Djava.ext.dirs指定其搜索路径,例如:java -Djava.ext.dirs=d:\classes HelloWorld;注意:如果将自己开发的 jar 文件放在System.getProperty(“java.ext.dirs”)所指定的目录中,也会被 ExtClassLoader类加载器加载。
public class Test {
public static void main(String[] args) {
String path = System.getProperty("java.ext.dirs");
String [] paths = path.split(";");
for (String string : paths) {
System.out.println(string);
}
}
}
执行结果:
可见引导类加载器所指定的java库是在D:\Program_Files\Java\jre1.8.0_131\lib\ext目录下。
三、AppClassLoader(系统类加载器)
该类加载器由sun.misc.Launcher$AppClassLoader类实现,负责加载System.getProperty(“java.class.path”)或CLASSPATH环境变量所指定的Java类,也可以加上-cp来覆盖原有的classpath设置,例如: java -cp ./classes HelloWorld;说明:默认情况下自定义类都由该类加载器加载。
public class Test {
public static void main(String[] args) {
String path = System.getProperty("java.class.path");
String [] paths = path.split(";");
for (String string : paths) {
System.out.println(string);
}
}
}
执行结果:
上面的输出结果与扩展类加载器和引导类加载器的输出结果有重叠,是为了保障如果两个父级类加载器出现问题确保类能够成功加载。
各级加载器之间的关系
在此验证一下:
public class Test {
public static void main(String[] args) {
ClassLoader classLoader= Test.class.getClassLoader();
System.out.println(classLoader.getClass().getName());
classLoader = classLoader.getParent();
System.out.println(classLoader.getClass().getName());
classLoader = classLoader.getParent();
System.out.println(classLoader);
//此时已经到达顶层的引导类加载器,此加载器是用c语言书写,不是java对象,因此为null
}
}
执行结果:
补充说明:
- sun.misc.Launcher源码:openjdk\jdk\src\share\classes\sun\misc目录
- 从上面的结果可以看出,并没有获取到ExtClassLoader的父ClassLoader,因为Bootstrap ClassLoader(启动类加载器)是用C语言实现的,getParent()找不到一个确定的返回父ClassLoader的方式,于是就返回null。
类加载器实例化过程:
通过java命令执行Java程序时,首先初始化JVM,产生Bootstrap ClassLoader(启动类加载器)——>Bootstrap ClassLoader自动加载Extended ClassLoader(扩展类加载器),并将其父ClassLoader设为Bootstrap Loader——>Bootstrap ClassLoader自动加载AppClass ClassLoader(系统类加载器),并将其父ClassLoader设为Extended ClassLoader。
类加载器的运行机制——双亲委派
一个类加载器要加载一个类时,首先会将此任务转给父类加载器,每个层次皆是如此,直至顶层的引导类加载器,如果引导类加载器能够在其搜索范围内完成加载,那么加载完成,反之交给下级尝试,以此类推,如果最终没有一个类加载器能够完成加载,那么就会抛出ClassNotFoundException异常,以上加载类的机制称为双亲委派机制。
我们查看ClassLoader抽象类中loadClass(String name, boolean resolve)方法的源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded首先查看类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {//没有加载,执行if代码块内容
long t0 = System.nanoTime();
try {
if (parent != null) {//判断是否有父类加载器
c = parent.loadClass(name, false);//有则转交
} else {
c = findBootstrapClassOrNull(name);//无则使用启动类加载器
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.如果依然没有找到,执行findClass找到class对象
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats定义自己的类加载器
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
自定义类加载器
无法改变JDK自带类加载器的搜索路径,因此如果在程序运行时从特定搜索路径加载类,就要自定义类加载器,其定义步骤如下:
- 自定义一个继承自ClassLoader抽象类的Java类;
- 重写ClassLoader抽象类中findClass方法;
package com.zzu.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class OtherClassLoader extends ClassLoader {
private String classPath;
public OtherClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
String className = classPath+"/"+name.replace(".", "/")+".class";//拼接完整路径
InputStream inputStream;
try {
inputStream = new FileInputStream(className);//获取class文件创建输入流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();//创建字节数组输出流
byte [] car = new byte[1024];
int length =0;
while((length=inputStream.read(car))!=-1) {
outputStream.write(car, 0, length);
}
byte[] b = outputStream.toByteArray();//转换为字节码数组
outputStream.flush();
outputStream.close();
return defineClass(name, b, 0, b.length);// 将字节码转化为Class对象
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
测试:
import com.zzu.test.OtherClassLoader;
public class Test {
public static void main(String[] args) {
String className="com.zzu.vo.Student";//待加载的类全名
String classPath = "D:/workspace/tt/bin";//待加载的类的路径
OtherClassLoader classLoader = new OtherClassLoader(classPath);
try {
Class clazz = classLoader.findClass(className);//类加载
System.out.println(clazz.getName());//加载成功,输出类名
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
执行结果:
自定义类加载器成功!
Class.forName()与ClassLoader区别
Java中Class.forName()和ClassLoader都可用来对类进行加载,但是它们之间也有不同的区别:
- Class.forName()除了将类的.class文件加载到JVM中之外,还会对类进行解释,执行类中的static块,还会执行给静态变量赋值的静态方法。
- ClassLoader仅仅是将.class文件加载到JVM中,只有在创建对象时才会去执行类中的static块。
public class Student {
static {
System.out.println("静态代码块");
}
}
public class Test {
public static void main(String[] args) {
try {
Class.forName("com.jd.bolg.Student");
System.out.println("------分割线------");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
ClassLoader classLoader = Test.class.getClassLoader();//任意获取一个类加载器
try {
classLoader.loadClass("com.jd.bolg.Student");//类加载
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
执行结果:
此外,也可以使用Class.forName(String name, boolean initialize, ClassLoader loader)加载类,如果initialize参数为true,则执行静态代码块,否则不执行静态代码块,此时只有创建对象时才会执行。