类加载时机
类的生命周期为:加载,连接(验证,准备,解析),初始化,使用,卸载。这5大阶段。
其中,加载,验证,准备,初始化,卸载,这5个阶段的顺序是固定的,解析是可以在初始化之后的。
对于类的加载,由虚拟机自行把控,但是类的初始化不是的,虚拟机严格规定了5种情况下必须对类进行初始化。
1.使用new 关键字实例化对象的时候,读取或设置一个类的静态字段,调用一个类的静态方法。
2.通过java.lang.reflect包对类进行反射调用的时候。
3.初始化一个类,其父类还没被初始化时,父类必须先进行初始化。
4.包含main()方法的类,虚拟机会优先进行初始化。
5.jdk1.7以后的动态语言支持的。
以下情况不会触发类的初始化。没有触发上面5种情况。
1.通过子类调用父类的静态变量。不会对子类进行初始化。
2.通过数组定义来引用类,A[] a=new A[10];A这个类不会触发初始化。
3.引用类的静态常量时不会触发初始化,因为这个量存放到了常量池里面。这个常量的引用会在编译期间进行传播优化,会将这个常量存到NOInitiazation类的常量池中以后这个类对常量的访问都会被转换为对NOInitiazation类的常量池的引用,因此不会对该类进行初始化。
加载
加载要做3个事情。
1.通过一个类的全限定名来获取定义此类的2进制字节流
2.将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构。
3.生成一个java.lang.Class对象来代表这个类,作为访问这个类的入口。
第一件事就是很开放的有以下方式读取。
1.从zip包中读取,比如从jar,war读取。
2.网络中读取,比如Applet.
3.运行时动态生成,比如java很经典的动态代理。
4.由其他文件生成
我们可以用虚拟机自身的类加载器来加载,也可以用我们自身的类加载器来获取字节流。
数组类的加载与众不同。
1.如果数组的组件类型是引用类型。数组将在加载这个类型的类加载器的类名称空间\上标示。
2.如果数组的组件类型不是引用类型,是常见类型,那么虚拟机会把数组标记为与引导类加载器关联。
类加载完成后将其放入java内存的中的方法区中(前面提到过java运行时数据分布存储),然后在java内存中(不一定是java堆)创建一个java.lang.Class类对象。
验证
验证Class文件字节流是否满足当前虚拟机要求。
1.文件格式验证
2.元数据验证,主要判断是否符合java语法。
3.字节码验证,保证程序语法是符合逻辑的。
4.符号引用验证,对类自身信息外的验证,看看相关类是否可以找到之类的。
准备
准备阶段为类变量分配内存,并且赋初始值。所需的内存从方法区里面获取,比如 public static int value=123;中,初始值为0,boolean为false,而123则是在初始化时才赋值上去。当然如果是final类型的量,那么是可以在准备阶段赋值123的。
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要在使用时可以定位到目标即可,符号引用和虚拟机的内存布局无关,引用的目标不一定加载到内存中。比如,在java编译时,java类不知道所引用类的实际地址,只能用符号引用来代替。
直接引用就是直接指向目标的指针,相对偏移量,或者句柄等。
初始化
类的初始化就是类加载的最后一步,也是执行类加载的最后一部分,初始化也就是执行类加载方法的过程。
1.类加载方法会自动收集所有类变量的赋值动作,和静态代码块合并成的。
2.类构造方法和类构造函数不一样,他不需要显示调用父类的构造函数,虚拟机会自动保证父类的类构造方法先执行。因此java.lang.Object类的类构造方法最先执行。
3.类构造方法不是必须的,如果类或者接口里面没有静态代码块,那就没有类构造方法。
4.接口不能有静态代码块,但是可以有静态变量,所以接口和类一样也会生成类构造方法,但是只有当负借口中定义的变量被使用时,父接口才会初始化。
5.虚拟机保证一个类在多线程环境中被正确的加锁,同步。