java虚拟机类加载机制:jvm把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析,初始化,最终形成可以被虚拟机直接执行的java类型。
主要有:加载->验证->准备->解析->初始化->使用->卸载。
下面进行逐个说明:
加载
<1>通过类的全限名称获取定义此类的二进制字符流(未规定必须从class文件中获取,jar zip,applet,运算时动态生成,jsp中获取)。
<2>字符流所代表的静态存储结构,转化为方法区的运行时数据结构,
<3>在内存中生成代表该类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。
编译,加载过程:
连接
<1>验证
确保类文件的字节流中包含的信息符合当前虚拟机的要求,并不会存在安全问题。
<2>准备
为类变量(static)分配内存,并设置变量变量初始值的阶段。
public static int value=12;
此时在准备阶段 为value分配的值为默认值 0 不是 12。
特殊情况:若为不可变常量 ,例如
public static final int value=12;
此时,value=12.
<3>解析
jvm将常量池中的符号引用直接替换为直接引用的过程。
初始化过程
初始化阶段是执行类构造器()方法的过程。
在以下四种情况下初始化过程会被触发执行:
1.遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需先触发其初始化。生成这4条指令的最常见的java代码场景是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用类的静态方法的时候。
注意:对于静态变量,获取其值时,只初始化其所在的类。
2.使用java.lang.reflect包的方法对类进行反射调用的时候
3.当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化
4.jvm启动时,用户指定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个类
在上面准备阶段 public static int value = 12; 在准备阶段完成后 value的值为0,而在初始化阶调用了类构造器()方法,这个阶段完成后value的值为12。
<1>类构造器()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句快可以赋值,但是不能访问。
<2>类构造器()方法与类的构造函数(实例构造函数()方法)不同,它不需要显式调用父类构造,虚拟机会保证在子类()方法执行之前,父类的()方法已经执行完毕。因此在虚拟机中的第一个执行的()方法的类肯定是java.lang.Object。
<3>由于父类的()方法先执行,也就意味着父类中定义的静态语句快要优先于子类的变量赋值操作。
*()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句,也没有变量赋值的操作,那么编译器可以不为这个类生成()方法。
<4>接口中不能使用静态语句块,但接口与类不同的是,执行接口的()方法不需要先执行父接口的()方法。只有当父接口中定义的变量被使用时,父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的()方法。
*虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步,如果多个线程同时去初始化一个类,那么只会有一个线程执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果一个类的()方法中有耗时很长的操作,那就可能造成多个进程阻塞。