jvm(三) 类加载机制、javac 编译
概述 Java 虚拟机把描述类信息的数据从 Class 文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被 jvm 直接使用的 java 类型,这个过程称为虚拟机的类加载机制。
类加载的时机一个类从被加载到虚拟机到卸载,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三部分称为链接。加载 ->验证->准备->初始化->卸载这五个阶段的顺序是不能变的,类型的加载必须按照这种顺序开始,而解析阶段不一定:它可以在某些情况下线进行初始化然后再进行解析,这是为了支持 java 的动态绑定特性。类的加载时机 《java 虚拟机规范》中并没有强制约束,有虚拟机具体实现,但是对初始化做出了严格规定有且只有六种情况必须对类进行初始化:①、遇到 new 、getstatic、putstatic 或者 invokestatic 这四条指令时,如果类没有进行过初始化,则需要先出发其初始化阶段。以下几种场景会生成这些指令;
使用 new 关键字实例化对象时候。
读取一个累的静态字段的时候(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)。
调用一个类的静态方法的时候。②、使用 java.lang.reflect 包的方法对类型进行反射调用的时候,如果类还没有初始化过,则出发初始化动作。③、初始化子类时,父类没有初始化时,先初始化父类④、当虚拟机启动时,执行包含 main 函数的类,会先初始化⑤、当一个接口中定义了 default (jdk8 之后)方法时,实现了此接口类,则该接口要在该类初始化之前初始化。⑥、当使用 jdk 7 新加入的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial 四种类型的方法句柄,并且这句柄对应的类么有初始化过,则需要先初始化。除了以上六种,其余的都是被动引用,不会出发初始化。如:public class A{static{System.out.println("Supper Class A init!");}public static int value = 1;}public class B extents A{static{System.out.println("Sub Class B init!");}}/**
非主动使用类字段,输出结果:Supper Class A init!*/pulic class NotInitSupperClass{public static void main(String[] args){System.out.println(B.value);}}
类加载过程 3.1、加载 1)、通过一个类全限定名来获取定义此类的二进制字节流(并没有限定从哪里获取,可以在网络,zip 压缩包、运行时生成,其他文件生成、数据库中读取,等等)。2)、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。3)、在内存总生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
3.2、验证
验证是链接的第一步,主要目的是确保 Class 文件的字节流中包含的信息都符合《java 虚拟机规范》的约束要求,保证不会有损坏虚拟机的代码;
是否以魔数 oxCAFEBABE 开头
主次版本号是否被当前虚拟机支持
常量池中的是否有不被支持的常量等等
3.3、准备
准备阶段是正式位类中静态变量分配内存,并设置变量初始值的阶段;
3.4)、解析
3.5)、初始化
4、类加载器
启动类加载器(BootStrap ClassLoader):主要加载放在 JAVA_HOME/lib 下面的类,或者被 -Xbootclasspath 参数制定的路径中存放的,并且能被 jvm 识别的类库;
扩展类加载器(Extension Class loader):主要加载 java_home/lib/ext 目录中的类,或者是被 java.ext.dirs 系统变量所制定的路径中的类库;
应用程序类加载器(Application ClassLoader):这个类加载器主要加载用户类路径上所有的类库;
用户自定义类加载器:实现 ClassLoader 接口重写 findClass()方法;以上四种加载器都有层级关系 bootStrap ClassLoader --->extension ClassLoader --->Application ClassLoader ------> 用户自定义类加载器;
双亲委培机制:类加载器在收到加载一个类的请求时,它首先不会自己马上去加载,而是把这个请求委派给父类加载器去完成,每一层加载器都是如此,因此所有的类加载请求都会传送到最顶层 bootStrap ClassLoader,只有当父类加载器无法完成时,自己才回去加载;
不同版本中 Abc 类的内容是不一样的,代码不同;
tomcat 自定义了 类加载器:
commons 类加载器:主要加载 tomcat 已经应用通用的一些类,位于 catalina_home/lib 下
catalina ClassLoader 用于加载服务器内部可见类,这些类应用程序不能访问
shared ClassLoader 加载应用程序共享类
web app ClassLoader 每个程序都会有一个独一无二的 webapp ClassLoader,它用来加载 WEB_INF/classes 和 WEB-INF/lib 下的类;
5、程序编译于代码优化
5.1、javac 总体过程大致分为 1 个准备和 3 个处理过程,分别如下:
1)、准备过程:初始化插入式注解器。
2)、解析于填充符号过程,包括:词法、语法分析。将源码中的字符流转换为标记集合,构造出抽象语法树。填充符号。产生符号地址和符号信息。
3)、插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。
4)、分析于字节码烧给你吃过程,包括:
标注检查,对语法的静态信息进行检查。
数据流以及控制流分析,对程序动态运行过程进行检查。
解语法糖。将简化代码编写的额语法糖还原为原有的形式。
字节码生成。
5.2、语法糖
java 中一些语法糖:
泛型、自动装箱、自动拆箱、遍历循环、变长参数、条件编译 、内部类、枚举类、断言语句、数值字面量、
范型:java 中的范型为“类型擦除式泛型” 也就是在编译后全部替换为原来的裸类型,与之相反的是 C#的“具现化式泛型”
自动装箱,拆箱于循环遍历:根据编译后的代码就可以知道使用遍历循环时为啥要实现 Iterable 接口的原因了。
评论