1、概念
类加载器把.class文件中的二进制数据读入到内存中,存放在方法区,然后在堆中创建java.lang.Class对象。
2、步骤
2.1、加载
识别字节码文件,并将类的二进制数据加载到JVM内存的方法区内。
2.2、验证
验证被加载类是否符号当前JVM的规范。
2.2.1、类文件结构检查
检查被加载类是否满足Java类文件的固定格式。
2.2.2、语法检查
检查被加载类是否符号Java语法规范。
2.2.3、字节码验证
检查由操作码组成的字节码流是否能安全执行。
2.2.4、二进制兼容性验证
检查相互引用的类之间是否协调一致。
2.3、准备
为静态变量分配内存并初始化为默认值。
2.4、解析
把类中的符号引用转化为直接引用,直接引用指向类中方法在方法区的内存位置。
2.5、初始化
为静态变量赋予用户指定的初始值。
3、类加载器
3.1、Bootstrap ClassLoader
C++编写,没有继承java.lang.ClassLoader类,负责加载JVM的核心库。例如%JAVA_HOME%/jre/lib下的rt.jar、resource.jar、charsets.jar。
3.2、Extention ClassLoader
Java编写,是java.lang.ClassLoader的子类,父类加载器是启动类加载器,负责加载JVM的扩展库,例如%JAVA_HOME%/jre/lib/ext目录下的jar包和字节码文件。通过设置java.ext.dirs能够添加用户的类库。
3.3、App ClassLoader(或System ClassLoader)
Java编写,是java.lang.ClassLoader的子类,父类加载器是扩展类加载器,负责从classpath下的字节码文件中加载类,是开发者开发Java代码的类加载器。
3.4、Customer ClassLoader
Java编写,是java.lang.ClassLoader的子类,父类加载器是应用类加载器,支持用户定制个性化的加载方式,例如从指定位置加载class文件、代码加密等。
package com.zy.demo.config;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
/**
* 自定义类加载器
* @author zy
*/
public class CustomerClassLoader extends ClassLoader{
//class文件目录
private static final String CLASS_FILE_PATH = "demoPath";
/**
* 将自定义类加载器委派父类加载器
*/
public CustomerClassLoader(){
super();
}
/**
* 以自定义类加载方式加载字节码文件
* @param name 字节码文件名称
* @return 类对象
* @throws ClassNotFoundException 未找到类异常
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//字节码文件输入
FileInputStream fis = null;
//字节数组输出
ByteArrayOutputStream baos = null;
//字节数组
byte[] bytes = null;
//字节数组长度
int len = 0;
try {
//从指定文件读取数据
fis = new FileInputStream(CLASS_FILE_PATH+name);
baos = new ByteArrayOutputStream();
//创建临时字节数组用于存储中间数据
byte[] tmp = new byte[1024];
int readSize = 0;
//输入
while((readSize = fis.read(tmp)) != -1){
//输出
baos.write(tmp,0,readSize);
}
bytes = baos.toByteArray();
len = bytes.length;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis != null){
fis.close();
}
if(baos != null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//将字节数组转化为Class对象
return super.defineClass(name,bytes,0,len);
}
public static void main(String[] args){
CustomerClassLoader ccl = new CustomerClassLoader();
try {
System.out.println(ccl.findClass("demo.class"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
4、双亲委派机制
4.1、概念
当类加载器收到类加载请求时,优先将该请求依次向上委派给父类加载器处理,直到Bootstrap ClassLoader。只有父类加载器无法加载类时,才会交给子类加载器处理。如果父类加载器与子类加载器都无法加载类时,则抛出ClassNotFindException。
4.2、意义
4.2.1、避免类的重复加载。
4.2.2、保护Java核心库不被篡改或替换。
4.3、自定义Java核心类加载
将自定义Java核心类(例如:java.util.ArrayList)编译成jar包,再放到Djava.endorsed.dirs指定的目录下(或者放到$JAVA_HOME/jre/lib/endorsed目录下),endorsed技术会优先加载指定jar包中的类。
4.4、自定义接口实现类加载
SPI(服务提供接口)机制:java.util.ServiceLoader
在resources资源目录下创建META-INF/services文件夹,然后在该文件夹下以全限定名创建接口文件,并在文件中配置接口实现类的全限定名。
package com.zy.demo.config;
import com.zy.demo.service.TestService;
import java.util.ServiceLoader;
/**
* 自定义SPI
* @author zy
*/
public class CustomerServiceLoader {
public static void main(String[] args){
//加载指定接口的ServiceLoader
ServiceLoader<TestService> serviceLoader = ServiceLoader.load(TestService.class);
//遍历接口实现类
for(TestService testService : serviceLoader){
//获取接口实现类名称
System.out.println(testService.getClass().getName());
//执行接口方法
testService.doTest();
}
}
}