类加载和初始化只进行一次
1,加载(需要类加载器的支持):这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
2,验证:这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
3,准备(都是默认值):准备阶段是正式为类变量分配内存并设置类变量初始值(通常情况下是数据类型的零值)的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中。
4,解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
5.初始化:执行类构造器clinit()方法的过程,这个方法是由编译器自动收集类中所有的类变量的赋值动作和静态语句块中的语句合并而成
类初始化阶段是类加载过程的最后一步,到了这个阶段才真正开始执行类中定义的Java程序代码(或者说是字节码)。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。
注意以下几点:
1.编译器收集的顺序是由语句在源文件中出现的顺序决定的,
静态语句块中只能访问到定义在静态语句块之前的变量,而定义在它之后的变量,在前面的静态语句块可以赋值,但不能访问,代码解释如下:
public class Test{ static{ i=0;//给变量赋值可以编译通过 sout(i);//编译器会提示非法向前引用 } static int i = 1; }
2.初始化方法执行的顺序,虚拟机会保证在子类的初始化方法执行之前,父类的初始化方法已经执行完毕,
3.虚拟机会保证一个类的clinit()方法在多线程环境中被正确加锁和同步
4.当访问一个java类的静态域时,只有真正声明这个域的类才会被初始化
类的主动引用(会发生初始化)
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 反射调用时
- 启动main方法所在的类
- 虚拟机会保证在子类的初始化方法执行之前,父类的初始化方法已经执行完毕,
类的被动引用(不会发生初始化)
- 引用常量不会引发类的初始化:常量在编译阶段就存入调用类的常量池中
- 通过数组进行类引用,不会触发初始化
- 访问一个静态域(静态变量)时,只有真正声明这个域的类才会被初始化(子类)
类加载器:
1.作用:将字节码文件加载到内存中,将静态数据转成方法区运行时数据结构,在堆中生成一个Class类对象,作为方法区类数据的方法的访问入口
层次结构(树状结构)
2.ClassLoader相关方法:
3,类加载器的代理模式
双亲委托机制