类的加载链接初始化都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为java应用程序
提供高度的灵活性。
1.加载:
装载类的第一个阶段
取得类的二进制流,从哪里获取是非常广泛的,可以从压缩包,网络读取,也可以运行时计算生成如动态代理技术,由其他文件生成,如由JSP文件生成对应的class类
转为方法区数据结构
在Java堆中生成对应的java.lang.Class对象
2.验证
目的:保证Class流的格式是正确的
文件格式的验证:是否以0xCAFEBABE开头、版本号是否合理(比如JDK7的东西,用在JDK6版本上)、常量池中是否有不被支持的类型、指向常量池中的索引值中是否有指向不存在的常量......
元数据验证:是否有父类、继承了被final修饰的类、非抽象类实现了所有的抽象方法、方法是否与父类产生矛盾(出现了不符合规范的重载)
字节码验证 (很复杂):运行检查、栈数据类型和操作码数据参数吻合、跳转指令指定到合理的位置、类的转换是有效的如把对象赋值给跟他毫无关系的数据类型是不合法的
符号引用验证:常量池中描述类是否存在、访问的方法或字段是否存在且有足够的权限(peivate,public,proected,default)
3.链接-准备
准备阶段是正式为类变量分配内存,并为类设置初始值 (方法区中)的阶段
易混淆的概念:
1.这时候进行内存分配仅仅包括类变量,不包括实际变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
2.这里所说的初始值通常情况下是数据类型的零值,例子如下
public static int v=1;
在准备阶段中,v会被设置为0
在初始化的<clinit>中才会被设置为1
对于static final类型,在准备阶段就会被赋上正确的值public static final int v=1;
4初始化
执行类构造器<clinit>()方法:
<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的;
如果一个类中没有静态语句块,也没有对变量的赋值操作,那么这个类就不会产生<clinit>()方法;
父类的<clinit>()方法总是先与子类的<clinit>()方法先执行,因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是Object类;
<clinit>()方法是线程安全的,如果多个线程同时初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法
对于什么情况下,开始类的加载虚拟机并没有进行强制规范,但是“”“有且只有”5种情况必须对类进行初始化:
1.遇到new/getstatic/setstatic/invokestatic这四条字节码指令时,场景分别是:new实例化对象时,读取或者设置一个类的静态字段,调用一个类的静态方法时
2.使用java.lang.reflect包的方法对类进行反射调用的时候
3.当初始化一个类的时候发现父类还没有被初始化,则需要先触发其父类的初始化
4.虚拟机启动时,用户要指定一个执行的主类(包含main方法的类),虚拟机会初始化这个主类
5.当使用jdk1.7动态语言支持时,如果java.lang.invoke.MethodHandle实例最后解析结果RFE_getStatic,REF_putStatic,REF_invokeStatic的方法句柄时,会触发其初始化
除此之外,所有引用类的方式都不会触发其初始化
例子:
通过子类引用父类的静态字段,不会导致子类初始化
通过数组定义来引用类,不会触发此类初始化Stadenet[] a =new Student();