共计 4480 个字符,预计需要花费 12 分钟才能阅读完成。
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 之后类加载委派关系的工作流程:
当平台及应用程序类加载器收到类加载申请后,在委派给父类之前,要先判断该类是否可能归属于零碎的一个模块中,如果有这样的归属关系,则优先委派给相干模块的加载器进行加载。
其委派关系如下图: