类的加载阶段,其实就是将class字节码文件中的二进制数据读取到内存之中,然后将文件所代表的静态存储结构,转换为方法区中运行时的数据结构,并且在堆内存中生成一个该类的java.lang.Class对象,作为访问方法区数据结构的入口。
而在同一个类加载器下,无论一个类被加载多少次,堆内存里面的Class对象都只有唯一 的一个。
而加载的方式也不仅仅是字节码文件或者说字节流的形式,还有我们都知道的动态代理,可以运行时动态生成字节码的字节流,从jar包中获取字节流等等,加载阶段不局限一种形式。
验证字节流的信息是否符合JVM的规范,保证不会出现危害JVM的代码。一般就是验证这个文件的格式,也就是头部信息是不是class文件,还有版本号,用高版本编译的文件是不能被低版本的兼容的;验证元数据的继承等进行语义分析是否合法;验证代码的控制流程等;还有就是符号引用的验证。
经过了验证之后,就会开始对该类对象的变量分配内存,并且设置初始值,而类变量的内存是分配在方法区中的,不同于实例变量。
这个阶段,其实就是把部分符号引用替换成直接引用。比如说
1 public class Tes1{ 2 static Simple simple; 3 4 public static void main(String[] args) { 5 System.out.println(simple); 6 } 7 }
在方法中引用了simple,那么在解析阶段就会在常量池找simple的符号引用,然后对simple加载,然后符号引用就会转换成直接引用,也就是simple的内存地址。
在这个阶段中,主要就是执行<clinit>()方法,在方法中,所有的类变量都会被赋予正确的值,以及执行静态语句块。
<clinit>()这个方法是在编译阶段,由编译器自动生成在class文件中的,方法里面的执行顺序与源代码中的书写顺序是一致的!!!也就是说这个方法里面是可以保证执行顺序的!!!!而且JVM会保证父类的<clinit>()先执行。除此之外,这个方法在多线程环境下,JVM会保证它的同步语义,有且仅有一个线程执行<clinit>(),并且只会执行一次。
原文:https://www.cnblogs.com/huangwenhao1024/p/14231959.html