标签:exce 第一步 关系 launcher 收集 引导 rfc turn path
虚拟机加载Class文件(二进制字节流)到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,这一系列过程就是类的加载机制。
类从被虚拟机加载到内存开始,直到卸载出内存为止,整个生命周期包括:加载——验证——准备——解析——初始化——使用——卸载 这7个阶段。其中验证、准备、解析3个部分统称为连接。
生命周期图如下:
其中加载、验证、准备、初始化、卸载这5个阶段顺序是确定的,类的加载过程必须按照这种顺序进行开始,而解析阶段则不一定:它在某种情况下可以在初始化之后再开始,这也是为了支持Java语言的动态绑定。
注意:所有引用类的方式都不会触发初始化(被动引用)例如:创建数组、引用final修饰的变量、子类引用父类的静态变量 不会触发子类初始化但是会触发父类初始化
加载是类加载的一个阶段,在加载阶段 虚拟机需要完成下面3件事情
相对于类加载的其他阶段,加载阶段(准确的说,是加载阶段中获取类的二进制字节流的动作)是开发人员可控性最强的。因为加载阶段既可以使用系统提供的引导类加载器来完成,也可以由开发人员自定义的类加载器来完成(即重写类加载器的loadClass()方法)。
加载完成后,外部的二进制字节流就转化成虚拟机所需的格式存储在方法区中,然后在内存中实例化一个java.lang.Class类的对象。这个对象将作为程序访问方法区中的这些类型数据的外部接口。
加载阶段与连接阶段的部分内容是交叉进行的,并不是加载完成后才能执行验证等操作。这些夹在加载之中的动作仍然属于连接阶段的内容,这两个阶段的开始时间仍然保持着固定的先后顺序。
验证是连接的第一步,为了保证加载的二进制字节流所包含的信息是符合虚拟机规范的。
验证阶段大致分为下面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种类加载器相互配合进行加载的。如果有必要,还可以加入自定义的类加载器。
这些类加载器之间的关系如下图:
双亲委派模型的工作过程是:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该到达顶层的启动类加载器。只有当父加载无法完成这个加载请求时,子加载器才会尝试自己去加载。
1、当ApplicationClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4、若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
标签:exce 第一步 关系 launcher 收集 引导 rfc turn path
原文地址:https://www.cnblogs.com/chenpt/p/9777367.html