虚拟机加载Class文件(二进制字节流)到内存,并对数据进行校验、转换解析和初始化,最终造成可被虚拟机直接使用的Java类型,这一系列过程就是类的加载机制。html
类从被虚拟机加载到内存开始,直到卸载出内存为止,整个生命周期包括:加载——验证——准备——解析——初始化——使用——卸载 这7个阶段。其中验证、准备、解析3个部分统称为链接。java
生命周期图以下:程序员
其中加载、验证、准备、初始化、卸载这5个阶段顺序是肯定的,类的加载过程必须按照这种顺序进行开始,而解析阶段则不必定:它在某种状况下能够在初始化以后再开始,这也是为了支持Java语言的动态绑定。数组
注意:全部引用类的方式都不会触发初始化(被动引用)例如:建立数组、引用final修饰的变量、子类引用父类的静态变量 不会触发子类初始化可是会触发父类初始化安全
加载是类加载的一个阶段,在加载阶段 虚拟机须要完成下面3件事情数据结构
相对于类加载的其余阶段,加载阶段(准确的说,是加载阶段中获取类的二进制字节流的动做)是开发人员可控性最强的。由于加载阶段既可使用系统提供的引导类加载器来完成,也能够由开发人员自定义的类加载器来完成(即重写类加载器的loadClass()方法)。多线程
加载完成后,外部的二进制字节流就转化成虚拟机所需的格式存储在方法区中,而后在内存中实例化一个java.lang.Class类的对象。这个对象将做为程序访问方法区中的这些类型数据的外部接口。源码分析
加载阶段与链接阶段的部份内容是交叉进行的,并非加载完成后才能执行验证等操做。这些夹在加载之中的动做仍然属于链接阶段的内容,这两个阶段的开始时间仍然保持着固定的前后顺序。this
验证是链接的第一步,为了保证加载的二进制字节流所包含的信息是符合虚拟机规范的。spa
验证阶段大体分为下面4个检验动做:
文件格式验证:验证字节流是否符合Class文件格式规范。例如:是否以魔数 0xCAFEBABE 开头、主次版本号是否在当前虚拟机处理范围内、常量池中的常量是否有不被支持的类型······。
元数据验证:对字节码描述的信息进行语义分析。例如: 这个类是否有父类、是否正确的继承了父类。
字节码验证:经过数据流和控制流的分析,肯定程序语义是合法的、符合逻辑的(说白了就是对类的方法体进行分析确保方法在运行时不会危害虚拟机)。
符号引用验证:确保解析动做能正常执行。
验证阶段是很是重要,但不必定是必要的阶段(由于对程序运行期没有影响)。若是所运行的所有代码都已经被反复使用和验证过,那么在实施阶段可使用-Xverify:none参数来关闭验证。
正式为类变量分配内存并设置类变量初始值。这些变量所使用的内存都将在方法区中进行分配。
注意:
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动做主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
符号引用:以一组符号来描述引用的目标,符号能够是任何形式的字面量。
直接引用:指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
初始化阶段是执行类构造器<clinit>()方法的过程。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员制定的参数值去初始化类变量和其余资源。
类构造器<clinit>()方法:是由编译器自动收集类中的全部类变量的赋值动做和静态代码块中的语句合并产生的。
编译器收集的顺序是由语句在源文件中出现的顺序决定的;静态代码块只能访问定义在静态块以前的变量,定义在它以后的变量,在前面的静态块中能够赋值,但不能访问。
非法向前引用示例 public class SuperClass { public static int va; static { value = 1; //能够编译经过 va = value; //报错 非法向前引用 System.out.println("父类初始化"); } public static int value = 123; }
<clinit>()方法 对类或接口来讲并非必须的,若是一个类中没有静态代码块,也没用对变量的赋值操做,那么编译器能够不为这个类生成<clinit>方法
接口中不能使用静态块,但仍能够有变量赋值操做,所以接口和类同样都会生成<clinit>方法。不一样的是,接口初始化不须要先执行父类的初始化,只有当父接口中的变量使用时,才会触发父接口的初始化。另外接口的实现类也不会触发接口的实例化。
虚拟机会保证一个类的<clinit>()方法在多线程中被正确的加锁、同步,若是多个线程去初始化一个类,那么只会有一个线程去执行类的<clinit>()方法,其余线程都处于等待状态。只能活动线程执行完毕。若是在一个类的<clinit>()方法中有耗时很长的操做,那就可能形成多个线程阻塞,在实际应用中这种阻塞每每是很隐蔽的。
虚拟机设计团队把类加载中的“经过一个类的全限定名来获取描述此类的二进制字节流”这个动做放到Java虚拟机外部去实现,以便让应用程序本身决定如何去获取所须要的类。实现这个动做的代码块称为类加载器。
启动类加载器(Bootstrap Classloader):负责将存放在<JAVA_HOME>\lib(Javahome即jdk的安装目录)目录中的,或者被-Xbootclasspath参数所指定的路径中的,而且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即便放在lib下面也不会被加载)类库加载到虚拟机内存中。启动类加载器没法被Java程序直接使用。
扩展类加载器(Extension Classloader):该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的系统路径中的全部类库。开发者能够直接使用扩展类加载器。
应用程序类加载器(Application Classloader):该加载器由sun.misc.Launcher$AppClassLoader实现,它负责加载用户类路径(ClassPath)上所指定的类库。开发者能够直接使用此加载器。若是应用程序中没有自定义的类加载器,那么这个就是程序默认执行的类加载器。(系统加载器)
咱们的应用程序都是由这3种类加载器相互配合进行加载的。若是有必要,还能够加入自定义的类加载器。
这些类加载器之间的关系以下图:
双亲委派模型的工做过程是:若是一个类加载器收到了一个类加载请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,所以全部的加载请求最终都应该到达顶层的启动类加载器。只有当父加载没法完成这个加载请求时,子加载器才会尝试本身去加载。
一、当ApplicationClassLoader加载一个class时,它首先不会本身去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
二、当ExtClassLoader加载一个class时,它首先也不会本身去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
三、若是BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
四、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,若是AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 先检查此类是否已被加载 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; } }
参考
《深刻理解Java虚拟机》
https://www.cnblogs.com/ityouknow/p/5603287.html