JVM类加载机制
概述
虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、装换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。
类加载过程
类加载过程:
- 加载:通过路径找到字节码文件,然后导入加载。
- 验证:检查字节码文件的正确性。
- 准备:为类中的静态变量分配内存空间。
- 解析:虚拟机将常量池中的符号引用替换为直接引用的过程。
- 初始化:为静态变量赋初始值,静态代码快执行初始化工作。
说明:一个Class文件被加载到内存中,会在内存中生成两块内容,一块是保存被加载的二进制文件;另一块是生成一个Class类对象,指向内存中的二进制文件。
类加载器
类加载器分类:
- 启动类加载器(Bootstrap ClassLoader):是虚拟机的一部分,默认加载jdk\lib下的核心类,C++实现,这个路径可以使用 -Xbootclasspath参数指定。
- 扩展类加载器(Ext ClassLoader):默认加载jdk\lib\ext\目录下扩展类,这个路径可以使用 java.ext.dirs系统变量来更改。
- 应用类加载器(App ClassLoader):负责加载开发人员编写的类,可以classpath指定内容。
- 自定义类加载器(Custom ClassLoader):自定义ClassLoader。
双亲委派机制
双亲委派过程:
- 当一个类收到类加载的请求时,虚拟机将这个请求交给Custom ClassLoader处理,先从ClassLoader缓存中查找,如果缓存中存在,则直接返回结果;如果缓存中不存在,则委派给它的父ClassLoader(即App ClassLoader)处理。
- App ClassLoader会先从缓存中查找,如果缓存中存在,则直接返回结果;如果缓存中不存在,则委派给他的父ClassLoader(即Ext ClassLoader)处理。
- Ext ClassLoader也会先从缓存中查找,如果缓存中存在,则直接返回结果;如果缓存中不存在,则委派给他的父ClassLoader(即Bootstrap ClassLoader)处理。
- Bootstrap ClassLoader已经是顶层了,没有父ClassLoader,如果这时缓存中仍然没有,则会先从核心库中查找,如果核心库中没有找到,则会给子ClassLoader(即Ext ClassLoader)反馈。
- Ext ClassLoader收到反馈,会查找扩展库,如果扩展库中不存在,则给子ClassLoader(即App ClassLoader)反馈。
- App ClassLoader收到反馈,会从应用代码中查找,如果没有找到,会给子ClassLoader(即Custom ClassLoader)反馈。
- Custom ClassLoader收到反馈,会交给自定义的ClassLoader处理,如果没有找到,则会抛ClassNotFoundException异常。
工作过程:类被加载时,首先自己不会先加载,而是交给父ClassLoader处理,父ClassLoader无法处理时,再交给子ClassLoader处理。
双亲委派机制优点
双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,Java核心API中定义类型不会被随意替换。假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
案例分析
public class Demo {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(sun.awt.HKSCS.class.getClassLoader());
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());
System.out.println(Person.class.getClassLoader());
System.out.println(Class.class.getClassLoader());
System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader());
System.out.println(Person.class.getClassLoader().getClass().getClassLoader());
System.out.println(Person.class.getClassLoader().getParent());
}
}
String.class.getClassLoader() 和sun.awt.HKSCS.class.getClassLoader() 返回null,这是因为Bootstrap ClassLoader是C++实现的核心库,在Java中没有对应类,只能返回null。sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader() 表示这个类位于扩展库中。Person.class.getClassLoader() 表示这个类是开发人员编写的。Class.class.getClassLoader() 和sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader().getClass().getClassLoader() 以及Person.class.getClassLoader().getClass().getClassLoader() 返回null,这是因为Class类位于rt包下,由是Bootstrap ClassLoader加载。Person.class.getClassLoader().getParent() 返回Ext ClassLoader,通过getParent() 获取它的父ClassLoader。
自定义ClassLoader
package com.example.lib_java;
public class Hello {
public void say() {
System.out.println("hello");
}
}
class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File f = new File("class文件全路径", name.replaceAll("\\.", "/").concat(".class"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b = fis.read()) != 0) {
baos.write(b);
}
byte[] bytes = baos.toByteArray();
baos.close();
fis.close();
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
public class Demo {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader classLoader = new MyClassLoader();
Class<?> clz = classLoader.loadClass("com.example.lib_java.Hello");
Hello h = (Hello) clz.newInstance();
h.say();
System.out.println(classLoader);
System.out.println(classLoader.getClass().getClassLoader());
System.out.println(classLoader.getParent());
}
}
|