一、概论:
一个java类从加载进内存到卸载出内存为止,一共经历7个阶段:
加载——>验证——>准备——>解析——>初始化——>使用——>卸载
其中,类加载包括5个阶段:
加载——>验证——>准备——>解析——>初始化
在类加载的过程中,以下3个过程称为连接:
验证——>准备——>解析
因此,JVM的类加载过程也可以概括为3个过程:
加载——>连接——>初始化
二、对这几个阶段进行详细解释:
①、加载:找到需要加载的类,并把类的信息加载到jvm的方法区中,然后在堆中实例化一个.class对象,作为方法区中这个类信息的入口。
②、连接:连接阶段分为 验证——>准备——>解析
验证:验证字节码格式、继承和实现是否符合标准等,总之,这个阶段就是保证加载的类能被jvm所运行。
准备:准备阶段就是为类中的静态变量分配内存,并给它们赋值为jvm默认初始值(不是用户自定义的初始值)
final修饰的静态常量的值存放在常量池中,赋值为为我们定义的值。
解析:这一阶段的任务就是把常量池中的符号引用转换为直接引用。
这样讲解析不易理解,所以搞了两个例子帮助理解:
1、比如org.simple.People类引用了org.simple.Language类,在编译时People类并不知道Language类的实际内存地址,因此只能使用符号org.simple.Language(假设是这个,当然实际中是由类似于CONSTANT_Class_info的常量来表示的)来表示Language类的地址。
2、比如我们要在内存中找一个类里面的一个叫做show的方法,显然是找不到。但是在解析阶段,jvm就会把show这个名字转换为指向方法区的的一块内存地址,比如c17164,通过c17164就可以找到show这个方法具体分配在内存的哪一个区域了。这里show就是符号引用,而c17164就是直接引用。在解析阶段,jvm会将所有的类或接口名、字段名、方法名转换为具体的内存地址
③、初始化:如果一个类被直接引用,就会触发类的初始化。在java中,直接引用的情况有:
1、 通过new关键字实例化对象、读取或设置类的静态变量、调用类的静态方法。
2、通过反射方式执行以上三种行为。
3、 初始化子类的时候,会触发父类的初始化。
4、 作为程序入口直接运行时(也就是直接调用main方法)。
除了以上四种情况,其他使用类的方式叫做被动引用,而被动引用不会触发类的初始化。
被动引用:1、子类调用父类静态成员属性时,父类被初始化,子类不被初始化(子类是间接引用,父类直接引用)。
2、一个类调用自己final属性修饰的静态变量时,该类不会被初始化,被final修饰的常量在Java代码编译的过程中就会被放入它被引用的class文件的常量池中(这里是A的常量池)。所以程序在运行期间如果需要调用这个常量,直接去当前类的常量池中取,而不需要初始化这个类。
接口的初始化 :接口和类都需要初始化,接口和类的初始化过程基本一样,不同点在于:类初始化时,如果发现父类尚未被初始化,则先要初始化父类,然后再初始化自己;但接口初始化时,并不要求父接口已经全部初始化,只有程序在运行过程中用到当父接口中的东西时才初始化父接口。