定义
是Java运行时环境(Java Runtime Environment)的一个部件,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。对学习类加载器而言,掌握Java的委派概念是很重要的。 每个Java类必须由某个类加载器装入到内存。Java程序可以通过类加载器来利用外部库 (维基百科)
类加载机制
把class文件的字节码内容加载到内存,对其进行校验,解析等操作,并将这些静态数据转换成方法区的运行时数据,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类的访问入口。
类的生命周期
- 加载
- 链接
- 初始化
类加载器
- Bootstrap ClassLoader
最顶层加载器,引导作用,加载Java核心类库如%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。属于JVM,只加载特定的类,其他的类哪怕手动放到其加载空间里也不会被加载。
- Extention ClassLoader
扩展类加载器,由sun.misc.Launcher.ExtClassLoader类实现,加载如%JRE_HOME%\lib\ext目录下的jar包和class文件
- App ClassLoader
也称为SystemClassLoader ,由sun.misc.Launcher.AppClassLoader实现,加载当前Java应用classpath的所有类。如果应用自己不定义类加载器,这个就是应用的默认加载器。
- Custom ClassLoader
自定义的ClassLoader,通过继承java.lang.ClassLoader类实现
JVM启动时,各ClassLoader的初始化顺序为从上到下。但加载顺序是从下到上。
特点
- 类加载器有自己特定的加载范围。
- 类的唯一性由类跟加载它的类加载器共同决定,也就是同一个class被不同类加载器加载,它在JVM的表现是不同的类,虽然他们是同一class文件。
- 通常情况下,我们使用JVM默认的三种加载器配合使用,即可完成基础加载,加载也是按需加载。加载过程基于双亲委托的机制,即优先由自己的父加载器去处理类加载。
双亲委托(parents delegate)
一个类加载器在接待类加载请求,首先不是自己亲自去加载类。而是委托给父加载器,如果父加载器可以完成这个类加载请求,就成功返回。只有父加载器完成不了,自己才去加载。
特性
- 委托:子加载器委托给父加载器加载
- 可见效:子加载器可以访问父加载器的加载范围,而反之则不行。
- 唯一性:保证每个类只被加载一次,比如加载Object.class,String.class这样的类都会委托到BootstrapClassLoader。保证其只被加载一起
优点
- 避免重复加载,父加载器先加载该类,子加载器没必要再加载,而是直接返回。
- 安全性,优先加载jdk的类,而不必担心某个核心类被篡改带来的安全性问题。比如核心类string会一直被bootstrapClassloader加载,自定义的String是无法替代的!
package java.lang;
public class SensoroClass {
public static void main(String[] args) {
System.out.println("SensoroClassLoader: " + SensoroClass.class.getClassLoader());
}
}
报错
ClassLoader类源码简析
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
//找不到或者已经加载则返回null
Class<?> c = findLoadedClass(name);
if (c == null) {
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.
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) {
//Links the specified class
resolveClass(c);
}
return c;
}
}
应用场景
-
资源隔离
有时候需要加载不同版本的jar,这时可以自定义类加载器
-
热部署
运行时想要更新class文件,达到热更新。那就少不了类装载器。因为类装载器不能再次加载一个已经加载的类,所以可以通过再来一个类加载器把再次加载一个加载好的类。这样还不需要重启应用程序。
-
代码保护
假如字节码文件有加密的需求,别人反编译也无法看到核心信息。我们就可以在自定义加载器里实现对它的解密,再加载进来进行使用。