JVM加载步骤
JVM加载步骤主要分为三步:加载、链接、初始化,其中链接可以分为校验、准备、解析。
1、加载
通过指定的className找到二进制码,生成class实例。ClassLoader就是用来加载class的。加载时,JVM需要完成的工作:
- 通过一个类的全限定名来获取其定义的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在java堆中生产一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
1.ClassLoader
类文件并非是立即加载到内存中的,而是根据程序运行需要加载的,ClassLoader负责类文件加载到内存中。
ClassLoader主要有四个:Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader、Custom LoaderClass.
从上图可以看出类加载器之间的关系,检查是否加载是自下而上检查,加载是自上而下去加载,失败了就执行下一个加载器。
- BootStrap ClassLoader: BootStrap 是最顶层的类加载器,它是用C++编写并且内嵌到JVM中的,主要用来读取java的核心类库。
- Extension ClassLoader: 负责加载java平台中扩展功能的jar包。
- App ClassLoader: 负责加载ClassPath中指定的jar包以及目录中Class。
- Custom ClassLoader:属于应用自定义的ClassLoader,可以根据j2e中规范自行实现。
在加载类时,先判断该类是否已经加载过了,如果未被加载,则委托其父类加载器去加载,这是一个不断向上搜索的过程,当该类所有的父类加载器都没有加载该类,则回到该类的加载器中去加载,如果加载不了就会抛出ClassNotFoundException.
ClassLoader抽象类的关键方法
- loadClass():此方法主要负责加载指定名字的类,ClassLoad的实现方法为先从已经加载的类中寻找,如果没有则继续从parent ClassLoader中寻找,如果仍然没有找到,则从System ClassLoader中寻找,最后调用findClass方法来寻找。
- findLoadedClass():此方法负责从当前ClassLoader实例对象缓存中寻找已经加载的类,调用的是native方法。
- findSystemClass():次方法负责从System ClassLoader中去寻找,如未找到,则继续从Bootstrap ClassLoader中寻找,如过仍然没有找到,则返回null。
2、类的加载
类的加载有三中方式:
- 命令行启动应用时候由JVM初始化加载
- 通过Class.forName()方法动态加载
- 通过ClassLoader.loadClass()方法动态加载
Class.forName()和ClassLoader.loadClass()区别
Class.forName:将类的.Class文件加载到jvm中之外,还会对类进行解释,执行类中的static块
3、双亲委派机制
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把加载请求委托给父加载器去完成,依次向上传递,因此类的请求最终都会被传递到顶层的启动类加载器中,只有在父加载器的搜索范围内没有找到所需的类时,子加载器才会尝试自己去加载。
机制:
- 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader.
- 当ExtClassLoader加载一个class时,它首先也不会自己去加载,而是把类加载请求委派给BootStrapClassLoader去完成。
- 如果BootStrapClassLoader加载失败,会让ExtClassLoader去加载
- 如果ExtClassLoader也加载失败,则会使用AppClassLoader来加载。
意义: - 系统类防止内存中出现多份同样的代码
- 保证Java程序安全稳定运行
2、链接
- 验证:目的是确保Class文件的字节流中包含的信息符合当前虚拟机要求,并且不会危害虚拟机自身的安全。
文件格式验证:验证字节流是否符合Class文件格式的规范;
元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
符号引用验证:确保解析动作能正确执行 - 准备:为类的静态变量分配内存,并初始化为默认值
准备阶段是正式为类变量分配内存并设置默认值的阶段,这些内存都是分配到方法区中的。需要注意的是此阶段的内存分配仅包含类变量(static),而不包含实例变量,实例变量会在实例化时随着对象一块分配在Java堆中。如果变量时static final,那么在编译时会为这个变量生成一个ConstantValue属性,在此阶段虚拟机就会根据这个属性设置给变量赋值。 - 解析:把类中的符号引用转换为直接引用
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用。符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
3、初始化
为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要是对类变量进行初始化。
JVM初始化步骤:
- 假如这个类还没有被加载和链接,则程序先加载并连接该类
- 假如这个类的父类还没有初始化,则先初始化父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机:
- 创建类的实例,就是new的时候
- 访问某个类或接口的静态变量,或对该静态变量赋值
- 调用类的静态方法
- 反射
- 初始化某个类的子类,则其父类也会被初始化
- Java虚拟机启动时被标明为启动类的类