Java类加载机制
Java类加载的生命周期
Java类加载的生命周期共分为7个阶段,分别是:加载、验证、准备、解析、初始化、使用、卸载。其中验证、准备、解析三部分称为连接。如下图:
- 加载
通过全限定名获取类的二进制字节流并将其静态存储结构转化为方法区的运行时数据结构,且在内存中生成类的Class对象。 - 验证
保证Class文件的字节流符合规范,不会危害虚拟机环境。主要有四种验证:文件格式验证、元数据验证、字节码验证、符号引用验证。 - 准备
将类中的定义的静态变量分配内存并设置类变量初始值。
eg:public static int value = 111;
中在准备阶段value
设置的值为0。
注意: 在对静态变量设置值时,常量例外,在准备阶段直接对常量赋值为程序员主观定义的值
eg:public static final int value = 122;
,那么在准备阶段value常量设置的值为122。 - 解析
将常量池中的符号引用替换为直接引用。 - 初始化
根据程序员程序编码制定的主观计划处初始化类变量和其他资源。
类加载器
JDK9之前,Java应用主要由三种类加载器相互配合来完成加载的。
启用类加载器——Bootstrap ClassLoader
该类加载器主要是使用C/C++实现,并不在Java类库中。
该加载器主要加载<JAVA_HOME>\Lib目录下或被-Xbootclasspath
参数制定路径中存放的并且Java虚拟机能够识别的类库。Java虚拟机安装名字识别,如rt.jar、tools.jar, 其中名字不符合放在lib文件下也不会被加载
- 扩展类加载器——Extension ClassLoader
该类是在sun.misc.Launcher$ExtClassLoader
中以Java代码的形式实现。
主要负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量制定路径中的所有类库。 应用程序类加载器——Application ClassLoader
该类是在sun.misc.Launcher$AppClassLoader
中以Java代码的形式实现。
主要负责加载用户类路径(ClassPath)上的所有类库。双亲委派模型
上述三种类加载器的关系如下图:
要求
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器的父子关系并非类的继承关系,而是加载器之间的关系。
工作过程
如果一个类加载器收到了类加载的请求,它不会首先自己尝试加载这个类,而是把请求委托给父加载器来完成,每一层的类加载器都是如此,因此所有的加载请求最终都应传到最顶层的启动类加载器中。只有当父加载器无法加载请求类时(在加载器搜索范围内未找到类)。子加载器才会尝试自己完成加载。
源码解析:
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;
}
}
优点
- 安全机制: 自己写的与核心API库相同的类不会被加载。
- 避免类的重复加载:当父类加载后,子类便不会加载,保证了被加载类的唯一性。
实现自定义符合双亲委派机制的类加载器
public class MyClassLoader extends ClassLoader {
private String path;
public MyClassLoader(String path) {
this.path = path;
}
/**
* 读取字节流
* @param name
* @return
* @throws IOException
*/
private byte[] getClass(String name) throws IOException {
String filePath = name.replaceAll("\\.", "/");
FileInputStream fileInputStream = new FileInputStream(path + "/" + filePath + ".class");
int len = fileInputStream.available();
byte[] data = new byte[len];
fileInputStream.read(data);
fileInputStream.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] aClass = new byte[0];
try {
aClass = getClass(name);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, aClass, 0, aClass.length);
}
}
打破双亲委派机制
Why
- 系统基础类型调用用户的代码(eg: JNDI)
- 程序的动态性(eg:Hot Deployment)
实现打破双亲委派机制的自定义类加载器
public class MyClassLoader extends ClassLoader {
private String path;
public MyClassLoader(String path) {
this.path = path;
}
/**
* 读取字节流
* @param name
* @return
* @throws IOException
*/
private byte[] getClass(String name) throws IOException {
String filePath = name.replaceAll("\\.", "/");
FileInputStream fileInputStream = new FileInputStream(path + "/" + filePath + ".class");
int len = fileInputStream.available();
byte[] data = new byte[len];
fileInputStream.read(data);
fileInputStream.close();
return data;
}
@Override
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();
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
if (!name.startsWith("classload")) {
c = this.getParent().loadClass(name);
} else {
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;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] aClass = new byte[0];
try {
aClass = getClass(name);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, aClass, 0, aClass.length);
}
}
模块化下的类加载器
JDK9之后类的加载机制发生了一些变化。
- 启动类加载器——Bootstrap ClassLoader在java中以BootClassLoader类实现
- 原本的ExtClassLoader扩展类加载器在JDK9之后被PlatformClassLoaders平台类加载器取代
JDK9之后的类加载委派关系
JDK9之后类加载委派关系的工作流程:
当平台及应用程序类加载器收到类加载请求后,在委派给父类之前,要先判断该类是否能够归属于系统的一个模块中,如果有这样的归属关系,则优先委派给相关模块的加载器进行加载。
其委派关系如下图:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。