JVM类加载机制
当使用java命令执行某个类的main方法是,首先要使用类加载器把主类加载到JVM中;
loadClass的大致流程
加载->验证->准备->解析->初始化->使用->卸载
加载:在硬盘中通过IO读取这个类的字节码文件;当对应的类在代码中被调用方法、创建对象等等,会加载这个类的字节码;
验证:验证读取的字节码问价是不是规范的java字节码文件;
准备:给静态变量分配内存,并赋默认值;
解析:将符号引用替换成直接引用,改阶段会把静态方法由方法名的符号引用,替换成该静态方法的所在内存的指针,这是静态链接过程;动态链接是指程序运行时把符号引用替换成直接引用;
初始化:对静态变量初始化指定得值,执行静态代码块。
类被加载到方法区中后,主要包括:运行时常量池、类型信息、方法信息、类加载器的引用、对应class的实例引用;
类加载器的引用:这个类的类加载器的实例引用;
对应class的实例引用:加载成功后会在堆中创建一个实例对象,方法区存放的是该实例对应方法的入口引用。
注:jvm的加载是懒加载,只有对应的类在程序中实际使用了(只引用不行),例如new了新对象,该类才会加载。
类加载器的双亲委派机制
类加载器的种类
引导类加载器:该类加载器是由C++实现的,负责加载jvm的核心类库,位于jre文件夹下的lib中的核心类库,例如rt.jar等;
扩展类加载器:该类主要加载jvm的扩展类库,为jre/lib/ext目录下的类库;
应用程序类加载器:该类主要加载用户自己写的类,即classpath路径下的类;
类加载器的初始化
在程序运行时会创建jvm启动器的实例Launcher,该实例是单例的,保证一个虚拟机只有一个Launcher;
在Launcher的构造方法中会创建两个类加载器,分别是ExtClassLoader和AppClassLoader;
JVM默认使用Launcher中的getClassLoader获取AppClassLoader来作为应用程序的类加载器。
双亲委派机制
JVM类加载器亲子关系图
首先说明父加载器不是说AppClassLoader继承了ExtClassLoader,只是在ClassLoader中有一个parent属性,AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null,引导类加载器是C++编写;ExtClassLoader和AppClassLoader都继承ClassLoader。
双亲委派机制是当一个类需要加载时,当前加载器先判断该类是否加载过,如果没有加载过,会委派父加载器加载该类,如果加载过,则返回该类;父加载器也会先判断是否加载过该类,如果加载过就返回该类,反之会委派上一级加载器加载;一直委派到引导类加载器,如果引导类加载器也没有加载过,则返回下一层类加载器加载,该加载器加载到,返回该类,加载不到使用下一层加载器,加载到了返回,加载不到继续下一层依次类推;
例如,加载自定义的一个类,首先通过AppClassLoader加载,如果没有加载过委托给ExtClassLoader加载,如果ExtClassLoader也没有加载过,使用引导类加载器加载,引导类加载器只加载jre的lib下的核心类库,肯定无法加载到自定义类;引导类加载器无法加载,使用ExtClassLoader类加载器加载,如果加载到,返回该类,如果加载不到,使用AppClassLoader加载。
为什么有双亲委派机制
1、沙箱安全:保证jvm的核心类库不会被篡改
2、避免类重复加载,父类加载过该类,子类又重新加载了一遍。
自定义类加载器
自定义类加载器只需要继承CLassLoader类,重新findClass方法即可;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyMemoClassLoader {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
// 要加载的类不要放在测试项目的编译路径下,重新找个地方放,否知会使用AppClassLoader加载
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("com.jvm.test.TestClassLoader");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
运行结果:
=======自己的加载器加载类调用方法=======
com.tuling.jvm.MyMemoClassLoader$MyClassLoader