Java中一个类的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段。其中准备、解析和初始化统称为连接。七个阶段的顺序如下:
类加载有以下三个阶段:
验证阶段主要校验Class字节流是否符合当前虚拟机的规范,分为文件格式验证、元数据验证、字节码验证、符号引用验证。
主要对字节码描述的信息进行语义分析,保证其描述符合Java语言的要求。主要进行以下验证:
最复杂的一个阶段。主要目的是通过数据量和控制流分析,确定程序语义是合法的,符合逻辑的。 保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
符号引用中通过字符串描述的全限定名是否能找到对应的类。 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。 符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。
准备阶段正式为类变量分配内存并设置初始值阶段。
public static int value=123;
初始后为 value=0;
对于static final类型,在准备阶段会被赋予正确的值: public static final value=123;
初始化为 value=123;
如果是boolean值默认赋值为:false。
如果是对象引用默认赋值为:null。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任意形式的字面量,只要使用时能够无歧义地定位到目标即可。
直接引用:直接引用可以是指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
解析又分为:类或者接口的解析、字段解析和接口方法解析。
JVM中主要有三种类型的类加载器:
在双亲委派模型中,如果一个类加载器收到了一个类的加载请求,它会首先委派给父类加载器来加载这个类,如果父类加载器加载不了再由自己来加载。这样所有的加载请求最终都会传送到顶层的启动类加载器中。这个过程如下图所示:
Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。
上面介绍了Java中的三种类型的类加载器,其中扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)的类结构如下图所示:
可以看到扩展类加载器和应用程序类加载器都继承了ClassLoader抽象类。 ``` public abstract class ClassLoader {
// 类加载器的父类,收到类加载请求后先委派给parent进行类加载,失败后再由自己加载 private final ClassLoader parent; // 此加载器已经加载过的类,这个表的唯一作用是保持对已经加载过的类的强引用。还记得之前我们说过一个Class被 // GC的条件吗?其中一项就是加载这个Class的ClassLoader已经被GC,如果加载这个Class的ClassLoader还未被GC // 则当前Class不能被GC,就是因为这里保持了对加载过的类的强引用。 private final Vector<Class<?>> classes = new Vector<>(); // 加载类的逻辑 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先获取要加载的类的类加载锁 // First, check if the class has already been loaded // 检查对应的Class是否已经被加载过,注意这里的findLoadedClass调用的是一个native方法,而不是上面所说的classes Class<?> c = findLoadedClass(name); if (c == null) { // 如果还未被加载,则加载 long t0 = System.nanoTime(); try { // 首先如果这个加载器的parent不为null(只有启动类加载器的parent为空),则委派给这个类加载器的父类加载器来进行加载 if (parent != null) { c = parent.loadClass(name, false); // 调用parent来处理这个加载请求 } else { c = findBootstrapClassOrNull(name); // 如果parent为空,则证明当前类加载器是启动类加载器 } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // 如果父类加载器没有加载到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; } } 复制代码
}
ClassLoader中的classes用于保持对它已经加载过的类的引用,这就保证了一个Class被GC的必要提交就是加载这个Class的ClassLoader已经被GC了,否则这个Class就还有强引用,不能被GC。 在loadClass方法中首先检查当前的Class是否已经加载过,如果没有加载则进行加载,否则直接返回这个Class。 首先检查类加载器的parent是否为空,如果不为空交由parent来加载这个类,如果parent为空则证明当前类加载器为启动类加载器。 如果父类加载器加载失败,则由当前类加载器自己去加载,也就是findClass方法,这个方法在ClassLoader中没有具体的实现,默认是抛`ClassNotFoundException`异常,需要子类去实现自己的加载逻辑。 最终判断加载完成的Class是否需要进行解析,如果需要解析则进行解析,然后返回这个Class。 ## 参考资料 《深入理解Java虚拟机——JVM高级特性与最佳实践》-周志明复制代码