三、类的加载机制
3.1类加载的时机
-个类型从被加载到虛拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个部分统称为连接。
类什么时候加载没有明确要求,但是类什么时候初始化有明确要求
有且只有六种情况必须立即对类进行“初始化”:
1.使用new实例化对象、 读写类的静态字段、调用类的静态方法时。
2.使用java.lang.reflect包的方法对类型进行反射调用时。
3.当初始化类时,若发现其父类还没有进行过初始化,则先初始化这个父类。
4.虚拟机启动时,需要指定个要执行的主类,虚拟机会先初始化这个主类。
。。。。。
3.2类加载的过程
3.2.1加载
在加载阶段,Java虚 拟机需要完成以下三件事情:
1.通过一个类的全限定名来获取定义此类的二进制字节流。(找到它)
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。(从磁盘上读到内存里)
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。(生成一个对象代表它)
对于数组类而言,情况就有所不同,数组类本身不通过类加载器创建,它是由Java虚拟机直接在内存中动态构造出来的。
加载阶段结束后,Java虛拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中了,方法区中的数据存储格式完全由虚拟机实现自行定义,会在Java堆内存中实例化一个java lang. Class类的对象,这个对象将作为程序访问方法区中的类型数据的外部接口。
准备阶段:
准备阶段是正式为类中定义的变量(静态变量)分配内存并设置类变量初始值的阶段,
关于准备阶段,还有两个容易产生混淆的概念需要强调。首先是这时候进行内存分配的仅包括类变量,而不包括实例变量。其次是这里所说的初始值“通常情况"下是数据类型的零值(默认值)。
除了这些类型外的默认类型为null
3.2.4解析
解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程。这两种引用的区别如下:
1.符号引用:符号引用以一组符号来描述所引用的目标.
2.直接引用:直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到且标的句柄。
3.2. 5初始化
类的初始化阶段是类加载过程的最后-个步骤,直到初始化阶段, Java虚 拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。本质上,初始化阶段就是执行类构造器< clinit>()的过程。< clinit>()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物,
- < clinit>()是 由合并产生的,编译器收集的顺序是由语句在源文编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句件中出现的顺序决定的,静态语句墙中只能访问到定义在静态语句块之前的变量。
静态块中只能访问他前面的静态变量,不能引用它后面的(因为编译时它们是按照顺序编译的)
3.由于父 类的< clinit>()先执行,也就意味着父类定义的静态语句块要优先于子类的变量赋值操作。
- < clinit>()对 于类或接口来说并不是必需的,如果一个类 中没有静态语句块,也没有对变量的赋值操作.那么编译器可以不为这个类生成< clinit>
- 接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类-样都会生 成< clnit>()。
但接口,与类不同的是,执行接口的< clinit>()不需要先执行父接口的< clinit>().因为只有当父接口中定义的变量被使用时,父接口才会被初始化。此外,接口的实现类在初始化时也一样不会执行接的< clinit>()。 - Java虛 拟机必须保证-个类的< clinit>()在 多线程环境中被正确地加锁同步,如果多个线程同时去初始化-个类,那么只会有其中一个线程去执行这个类< clinit>(),其他线程都需要阻塞等待,直到活动线程执行完毕< clinit>()。
调用子类的静态代码块时,是子类的先执行还是父类的先执行?
父类的,因为父类的< clinit>先执行
3.3.2双亲委派模型
对于JDK8及其之前版本的Java应用,都会使用到以下3个系统提供的类加载器来进行加载:
1.启动类加载器: 这个类加载器负责加载存放在< JAVA HOME>lib目录.或者被- Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的类库加载到虚拟机的内存中。
2.扩展类加载器:它负责加载< JAVA HOME>\lib\ext目录中,或者被java ext dirs系统变量所指定的路径中所有的类库。
3.应用程序类加载器:它负责加载用户类路径. (classpath)上所有的类库、 开发者同样可以直接在代码中使用这个类加载器。
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承的关系来实现的,而是通常使用组合关系来复用父加载器的代码。(调用关系)
3.3.3双亲委派模型被破坏的场景
1.双亲委派模型的第一次“被破坏"发生在双亲委派模型出现之前
2.双亲委派模型的第二次"被破坏”是由这个模型自身的缺陷导致的
基础类(jdk内嵌的类)启动服务时要调用用户的类,但是它不会认识我们写的类,怎么办?
线程上下文类加载器
3.双亲委派模型的第三次 被破坏是由于用户对程序动态性的追求而导致的。