在java语言中,类的加载,链接和初始化都是在运行期间发生的,所以java语言天生就可以实现动态拓展
1.类的生命周期
加载->验证->准备->解析->初始化->使用->卸载 验证+准备+解析 叫做连接(Linking) 类的生命周期共有7步,其中加载,验证,准备,使用,卸载的顺序是固定的,但是解析和初始化不是固定的,某些情况下解析可以在初始化后面进行1.加载
加载过程中需要完成三件事情: 1.通过类的全限定名来获取此类的二进制字节流 2.将这个字节流代表的静态存储结构转化为方法区的运行时数据结构 3.在内存中生成java.lang.class对象,作为方法区这个类各种数据的访问入口(Class对象虽然是对象但是存在于方法区中) 加载过程中可以使用自己定义的加载器来控制字节流2.验证
虽然通过java源码编译出来的字节流是相对安全的,但是也有可能字节流并不是通过源码编译出来的,而是经过恶意修改的,可能会使系统崩溃,所以java虚拟机要进行验证 验证过程有三步: 1.文件格式验证 2.元数据验证 3.字节码验证 4.符号引用验证3.准备
准备阶段是正式为类变量分配内存并且设置类变量初始值的阶段 注意: 1.这里仅对类变量,static修饰的变量,而不是实例变量初始化. 2.这里的初始值是通常意义下的零值,如 public static Int value = 5; 经过准备过,value的值其实为0 3.特殊情况下:如public static final Internet value = 5;就会被设置为5;4.解析
解析过程就是虚拟机将常量池里面的符号引用转换为直接引用, 虚拟机没有要求解析的时间,只要求了在 new .instanceof, getstatic等16个用于操作符号引用的字节码指令前解析 1.除invokedynamic外,对第一次解析的结果进行缓存(在运行时常量池中记录直接引用,并且将常量标记为已解析状态) 2.对于invokedynamic指令,他的引用为动态调用点限定符,等到执行时,才开始解析常量池中有七种常量类型,下面有七种解析过程:
.....
5.初始化
java虚拟机规格规定了5种情况下,必须对类进行初始化 1.遇到new,getstatic,putstatic,或者invokestatic时,如果类没有进行初始化,则必须进行初始化 通常在下面情况下初始化:new一个实例时,调用类的静态方法,或者静态字段时(final标记的除外,他会在编译器就将结果放入常量池中) 2.使用java.util.reflect对类进行反射时 3.初始化一个实例时,如果父类还未初始化,先触发父类初始化 4.虚拟机启动,需要一个执行的主类(含有main方法的那一个),必须先初始化这个主类 5.使用jdk1.7的动态语言支持时,一个java.lang.invoke.MethodHandle实例最后解析结果是getstatic,putstatic.invokestati句柄,这个对应的类要先初始化例子
package day20150908;
public class LoadTest {
static{
System.out.println("LoadTest init");
}
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
class SuperClass{
static{
System.out.println("SuperClass init~");
}
public static int value =1;
}
class SubClass extends SuperClass{
static{
System.out.println("SubClass init~");
}
}
运行结果: LoadTest init
SuperClass init~
1
main方法被执行前,先初始化loadtest类 因为只调用了父类的静态变量所以只需要初始化父类,并不需要初始化子类
类初始化是类加载过程的最后一步,前面的类加载过程中,处理加载阶段可以使用自己的加载器,其他都是虚拟机完成的,到了初始化这一步才是引用java程序代码 初始化过程是执行类构造器<Clint>()方法的过程