1、什么是类的加载
类的加载就是将类的class文件中的二进制字节流读入内存,并为之创建一个java.lang.class对象,也就是说当程序中使用任何类时,系统都会为之建立一个java.lang.class对象。
2、类的生命周期
类被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期就是包括:
(1)加载,加载类的class文件(并不一定是.class文件,也有可能是jar,war包等)中的二进制字节流读入内存,并为之创建一个java.lang.Class类的对象
(2)验证,文件格式、元数据、字节码、符号引用验证
(3)准备,为类的静态变量分配内存,并初始化默认值
(4)解析,把类中的符号引用转换为直接引用
(5)初始化,为类的静态变量赋予正确的初始值
(6)使用,new出对象程序中使用
(7)卸载,执行垃圾回收
通常加载、验证、准备、初始化、卸载这五个阶段顺序是确定的,解析阶段有可能在初始化阶段之后再开始,这是为了支持java语言的运行时绑定(动态绑定或晚期绑定)。它们开始执行的时候,有可能在一个阶段执行的过程中调用另一个阶段。
加载阶段,jvm需要完成3件事?
(1)通过类的全限定名获取该类的二进制字节流
(2)将字节流所带变的静态存储结构转化为方法区运行时的数据结构
(3)在内存中生成一个该类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
3、类加载过程
(1)加载:加载类的class文件中的二进制字节流读入内存,并为之创建一个java.lang.Class类的对象
(2)连接:连接阶段负责把类的二进制数据合并到JRE中。
①验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。验证阶段并不是必须的,可以通过设置Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
文件格式验证:主要验证字节流是否符合class文件格式规范,并能被当前虚拟机处理。(文件格式验证目的:就是把字节流能正确的解析并存储到方法区中)
例:常量池中是否有不被支持的常量类型等
元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范
(元数据验证目的:对类的元数据信息进行语义校验,保证不存在不符合java语言规范的元数据信息)
例:这个类是否有父类(除了java.lang.object之外,所有类都应该有父类)
这个类是否继承了不允许的被继承的类(被final修饰的类)
字节码验证:最重要的环节,通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。 要是要保证引用一定会被访问到,不会出现类等无法访问的问题。
例:符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问
符号引用验证:发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三个阶段解析阶段中发生。
②准备:为类的静态变量分配内存,并设置默认初始值(final修饰的静态常量在这个阶段直接赋值)
③解析:将常量池中的符号引用替换成直接引用。
说明一下:符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。
直接引用:是指向目标的指针,相对偏移量或者能够间接定位到目标的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
(3)初始化
初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。
4、类加载时机
(1)创建类的实例,也就是new一个对象
(2)访问某个类或接口的静态变量,或者对该静态变量赋值
(3)调用类的静态方法
(4)初始化一个类的子类,会首先初始化子类的父类
注意:final修饰的静态变量,如果该变量的值在编译的时候就能确定下来,那么这个变量相当于“宏变量”。java编译器会在编译时直接把之歌变量出现的地方替换成它的值。因此程序中使用该变量时,也不会导致类的初始化。
5、类加载器
实现类的加载动作,在java虚拟机外部实现,以便让应用程序自己决定如何获取所需要的类。
从java虚拟机的角度来讲,只有两种不同的类加载器:一种是启动类加载器,这个是类加载器使用c++语言实现的,是虚拟机自身的一部分;另一种是所有其他的类加载器,这些类加载器都有java语言实现,独立于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader。
从java开发人员角度看,类加载器分为以下三种:
(1)启动类加载器:负责加载JAVA_HOME/lib目录中,或者通过-Xbootclasspath参数指定路径中的,并且被虚拟机认可的(rt.jar)类库
(2)扩展类加载器:负责加载JAVA_HOME/lib/ext目录中,或者通过java.ext.dirs系统变量指定路径中的类库
(3)应用程序类加载器:负责加载用户路径(classpath)上的类库。默认是这个类加载器
类加载器之间的层次关系:
类加载器之间的这种层次关系叫双亲委派模型
双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间父子关系不是通过继承实现的,而是组合实现的。
双亲委派模型的工作过程:
如果一个类加载器接收到类加载请求,他自己不会去加载这个类,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器。只有当父类加载器无法完成这个加载请求时,子加载器才会尝试自己去加载。
双亲委派模型的好处:1、避免重复加载,父类加载过的类,子类无需重复加载
2、更加安全,一个类通过双亲委派模型传递到启动类加载器,启动类加载器发现它已经被加载,并不会重新加载这个类,而是返回加载过的class文件,这样防止核心api库被随意篡改。
6、类加载机制
java的类加载机制主要分为3种:
全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。