1 定义
顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中【只有加载到JVM中,JAVA程序才能使用该类】。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码存入到JVM的方法区中。并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 **newInstance()**方法就可以创建出该类的一个对象,存储在JVM中的堆空间处,封装该类在方法区中所有数据结构信息
从加载到JVM的过程中,描述了类的生命周期。


2 类加载器类型
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
加载路径为 - 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(app class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求
类加载的加载顺序为BootsTrap Classloader>Extensions Classloader > AppClassloader
且每个加载器都有父加载器
public class Test {
public static void main(String[] args) {
ClassLoader loader=Test.class.getClassLoader();
//sun.misc.Launcher$AppClassLoader@b4aac2 Test类的类加载器为AppClassLoader
System.out.println(loader);
//父加载器 sun.misc.Launcher$ExtClassLoader@154617c Test类的类加载器的父加载器为ExtClassLoader
System.out.println("Test parent》"+loader.getParent());
//父级的父级加载器 BootTrapClassLoader 打印为null ,是由c++实现的 是JVM的一部分,所以在JAVA代码中无法获得相关的引用
System.out.println("Test parent' parent 》"+loader.getParent().getParent());
ClassLoader loader1=String.class.getClassLoader();
//String等基础类是由BootStrap类加载器进行加载
//BootStrap类加载器 是由c++实现的 是JVM的一部分,所以在JAVA代码中无法获得相关的引用
System.out.println("String parent》");
//C:\Program Files (x86)\Java\jdk1.8.0_171\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.8.0_171\jre\lib\rt.jar;
//C:\Program Files (x86)\Java\jdk1.8.0_171\jre\lib\sunrsasign.jar;C:\Program Files (x86)\Java\jdk1.8.0_171\jre\lib\jsse.jar;
//C:\Program Files (x86)\Java\jdk1.8.0_171\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.8.0_171\jre\lib\charsets.jar;
//C:\Program Files (x86)\Java\jdk1.8.0_171\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.8.0_171\jre\classes
System.out.println("BootTrap加载目录"+System.getProperty("sun.boot.class.path")+"\n");
//当前加载路径可以使用指令进行切换 -D java.ext.dirs
//C:\Program Files (x86)\Java\jdk1.8.0_171\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
System.out.println("Extension ClassLoader加载目录"+System.getProperty("java.ext.dirs")+"\n");
System.out.println("AppClassLoader加载目录"+System.getProperty("java.class.path")+"\n");
}
}
3双亲委派机制
类加载的过程是通过**“委托”**机制实现的。
1:请求AppClassLoader 类加载器进行加载,判断是否有缓存,存在的话结束加载过程
2:1步骤时,无缓存的class信息,向上委托ExtClassLoader进行加载,判断是否有缓存,存在的话结束加载过程,否则进行步骤三
3:2步骤时,无缓存的class信息,向上委托Bootstrap进行加载,判断是否有缓存,存在的话结束加载过程,否则进行步骤四
4:3步骤时,无缓存信息,去BootstrapClassLoader规定的路径中进行加载,存在时结束加载过程,并在缓存中存储类的缓存信息,否则向下进行委托查找
5:4步骤时,未加载到相应的类信息,去ExtClassLoader规定的路径中进行加载,存在时结束加载过程,并在缓存中存储类的缓存信息,否则向下进行委托查找
6:5步骤时,未加载到相应的类信息,去AppClassLoader 规定的路径中进行加载,并在缓存中存储类的缓存信息
总结:
1每个类加载器对他加载过的类都有一个缓存,
2 向上委托查找,向下委托加载

4自定义加载器
- 编写一个类继承自ClassLoader类
- 复写它的findClass()方法
- findClass()方法中调用defineClass
//自定义类加载器用于加载Classe二进制文件
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath){
this.classPath=classPath;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
//需要加載的全路徑
classPath=classPath+name.replace(".","\\").concat(".class");
File file=new File(classPath);
//文件流中读取的二进制数据
byte[] t;
int length=0;
try {
FileInputStream fis=new FileInputStream(file);
ByteArrayOutputStream fos=new ByteArrayOutputStream();
while ((length=fis.read())!=-1){
fos.write(length);
}
t=fos.toByteArray();
return defineClass(name,t,0,t.length);
} catch (Exception e)
{
e.printStackTrace();
throw new ClassNotFoundException("类文件信息错误");
}
}
}
被加载的类信息【需要提前编译成为class文件】
package classLoader;
/**
* @author lyf
* @projectName Highly_Concurrent_Programming
* @date 2022/2/15 下午 03:32
* @description
*/
public class SalaryCalculator {
public double cal(double salary){
return salary*1.5;
}
}
测试类加载Class文件调用cal方法
public class SalaryCalculatorMain {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//被加载类的class路径
String classPath="D:\\LYF\\Highly_Concurrent_Programming\\JavaPractice\\Thread\\target\\classes\\";
MyClassLoader loader=new MyClassLoader(classPath);
//根據class的名字加載class二進制數據
//被加载的类路径名classLoader.SalaryCalculator
Class<?> salaryCalculator = loader.loadClass("classLoader.SalaryCalculator");
//获得实列
Object o = salaryCalculator.newInstance();
double salary=1000;
//获得类中的cal(double s)方法
Method cal = salaryCalculator.getMethod("cal",double.class);
//执行该实例的cal方法
Object invoke = cal.invoke(o, salary);
System.out.println("工資為"+(Double) invoke); //工資為1500.0
}
}
5 热加载
对jvm方法区中类定义进行替换,因为堆(heap)中的Class对象是对方法区对象的封装,所以可以理解为对Class对象的替换,
当一个class被替换后,系统无需重启,替换的类会立即生效。
实现方法:创建类实例时,需要重复地创建该类的类加载器【因为每个类在加载时,由于缓存的存在,仅仅只会加载一次】,从而达到实时更新class信息
缺点:类加载器的频繁创建,占用jvm的内存空间
忽略类加载过程中的Linking步骤,会使得本应该在编译阶段抛出的异常延时抛出
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) {
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) {
resolveClass(c);
}
return c;
}
}
477

被折叠的 条评论
为什么被折叠?



