我的面试经之JVM(二)类加载器子系统ClassLoader类的加载过程
ClassLoader,类加载器子系统:
负责从文件系统或网络中读取class文件,class文件所在开头有特定的文件标识
ClassLoader只负责class文件的加载,是否能运行,则由Execution Engine决定
加载类的信息存放在一块称为方法区的内存空间。除了类的信息,方法区还可能会存放运行时常量池的信息,还可能包括字符串字面量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)
举例:
类的加载过程:
加载阶段:
1、通过类的全限定名获取定义此类的额二进制流
2、将字节流所代表的静态存储结构转化为方法区(<7叫永久代)的运行时数据结构
3、在内存生成一个代表此类的java.lang.Class对象,作为方法区这个类的各种数据访问入口
加载.class方式:
链接(linking)阶段:
验证(Verify): 确保Class文件额字节流中包含的文件信息符合当前虚拟机要求,保证被加载类的正确性,不危害虚拟机自身安全
主要包括四种验证方式:文件格式验证、元数据验证、字节码验证、符号引用验证
准备(prepare): 为类变量分配内存并设置该类变量为0值
final修饰的static不包含在内,因为这样修饰的变量就成为一个常量,编译时就会分配,这个阶段会显示初始化
这里不会为实例变量分配初始化,类变量会分配在方法区,实例变量会随着创建对象分配到Java堆中
解析(Resolve): 将常量池内的符号引用转换为直接引用的过程【符号引用就是一组符号描述所引用的目标。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄】
事实上,解析操作是在JVM初始化之后进行的
解析动作主要针对类或接口、字段、类方法接口方法、方法类型等。
初始化(Initialization)阶段:
初始化就是执行类构造器方法<clinit>()的过程
此方法不用定义,由javac编译器自动搜集类中的所有类静态变量(口误没说)赋值动作和静态代码块中的语句合并而来【如果没有赋值动作或者静态代码,那么也不会有<clinit>()】
构造器方法中指令按照在源文件中出现的顺序执行
<clinit>()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>()方法);【任何一个类至少有一个构造器,就是init构造器】
若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()执行完毕 ,如下图注释。
虚拟机必须保障一个类的<clinit>()方法在多线程下被同步加锁
注意:静态代码块中可以给变量赋值,但是在声明之前不能调用,如下图: