Java 的类加载模型
Java 中的类是在运行期间第一次使用的时候动态加载到内存中的, 而不是一次性将所有的类都加载的内存中的. 因为如果一次性加载, 就会占用太多的内存. Java 中一个类的生命周期如下(载入, 验证, 准备, 解析, 初始化, 使用, 卸载):
Java 类加载过程
Java 的类加载过程一般包含以下几个阶段: 载入 -> 验证 -> 准备 -> 解析 -> 初始化. 这几个阶段一般是顺训发生的, 但是在动态绑定的情况下, 解析阶段发生在初始化之后.
-
载入 (Loading)
加载过程主要完成以下三件事:
- 通过类的完全限定名称获得定义该类的二进制字节流;
- 将该字节流表示的静态存储结构转换为方法区的运行时存储结构;
- 在内存中生成一个代表该类的
Class
对象, 作为方法区中该类各种数据的访问入口.
-
验证 (Varification)
在这个阶段 JVM 会对二进制字节流进行校验, 只有符合 JVM 字节码规范才能被 JVM 正确执行. 该阶段是保证 JVM 安全的重要屏障.
-
准备 (Preparation)
在准备阶段, JVM 会为类变量 (被
static
修饰的变量) 分配内存并设置初始值, 使用的是方法区的内存.实例变量不会在这个阶段分配内存, 它会在对象实例化时随对象在堆中分配内存.
如果类变量是常量 (被
static final
修饰的变量), 那么它将被初始化为表达式所定义的值而不是 0 值. -
解析 (Resolution)
在这个阶段 JVM 将常量池的符号引用替换为直接引用.
-
初始化 (Initialization)
初始化阶段才真正开始执行类中定义的 Java 程序代码. 初始化阶段是虚拟机执行类构造器
<clinit\>()
方法的过程. 在准备阶段, 类变量已经赋过一次系统要求的初始值, 而在初始化阶段, 根据程序员通过程序制定的主观计划去初始化类变量和其它资源.<clinit>()
是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的, 编译器收集的顺序由语句在源文件中出现的顺序决定. 特别注意的是, 静态语句块只能访问到定义在它之前的类变量, 定义在它之后的类变量只能赋值, 不能访问.
Java 类加载器
对于任意一个类, 都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性. 也就是说, 如果两个类的加载器不同, 即使两个类来源于同一个字节码文件, 那这两个类就必定不相等. 这里的相等, 包括类的 Class
对象的 equals()
方法, isAssignableFrom()
方法, isInstance()
方法的返回结果为 true
, 也包括使用 instanceof
关键字做对象所属关系判定结果为 true
.
从 Java 虚拟机的角度来讲, 只存在以下两种不同的类加载器:
- 启动类加载器 (Bootstrap ClassLoader), 使用 C++ 实现, 是虚拟机的一部分.
- 所有其它类的加载器, 使用 Java 实现, 独立于虚拟机, 继承自抽象类
java.lang.ClassLoader
.
从 Java 开发人员的角度看, 类加载器可以划分得更细致一些:
- 启动类加载器 (Bootstrap ClassLoader)
- 扩展类加载器 (Extension ClassLoader)
- 应用程序类加载器 (Application ClassLoader)
双亲委派模型
如果以上三种类加载器不能满足要求的话, 程序员还可以自定义类加载器 (继承 java.lang.ClassLoader 类), 它们之间的层级关系如下图所示.
这种层次关系被称作为双亲委派模型: 如果一个类加载器收到了加载类的请求, 它会先把请求委托给上层加载器去完成, 上层加载器又会委托上上层加载器, 一直到最顶层的类加载器; 如果上层加载器无法完成类的加载工作时, 当前类加载器才会尝试自己去加载这个类. 使用双亲委派模型能够使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系, 从而使得基础类得到统一.
自定义类加载器
以下代码中的 FileSystemClassLoader
是自定义类加载器, 继承自 java.lang.ClassLoader
. 它首先根据类的全名在文件系统上查找类的字节代码文件 (.class
文件), 然后读取该文件内容, 最后通过 defineClass()
方法来把这些字节代码转换成 java.lang.Class
类的实例.
java.lang.ClassLoader
的 loadClass()
实现了双亲委派模型的逻辑, 自定义类加载器一般不去重写它, 但是需要重写 findClass()
方法.
package com.demo;
import java.io.*;
public class FileSystemClassLoader extends ClassLoader{
private final String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = classNameToPath(name);
byte[] bytes = loadClassData(path);
if (bytes != null) {
return defineClass(name, bytes, 0, bytes.length);
} else {
throw new ClassNotFoundException();
}
}
private byte[] loadClassData(String path) {
FileInputStream inputStream = null;
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
inputStream = new FileInputStream(path);
byte[] buffer = new byte[1024];
int cnt;
while ((cnt = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, cnt);
}
inputStream.close();
return outputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private String classNameToPath(String name) {
return rootDir
+ File.separatorChar
+ name.replace('.', File.separatorChar)
+ ".class";
}
}
测试类
package com.demo;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
ClassLoader classLoader = Main.class.getClassLoader();
while (classLoader != null) {
System.out.println(classLoader);
classLoader = classLoader.getParent();
}
FileSystemClassLoader fileSystemClassLoader = new FileSystemClassLoader("/Users/zony/workingspace");
String packageNamePath = "com.demo.Test";
Class<?> log = fileSystemClassLoader.loadClass(packageNamePath);
System.out.println(log.getClassLoader());
Method method = log.getMethod("main", String[].class);
Object o = log.newInstance();
method.invoke(o, (Object) new String[]{""});
}
}
输出如下:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@6e0be858
com.demo.FileSystemClassLoader@610455d6
Hello, world!
Process finished with exit code 0