JVM虚拟机的类加载机制
本文探讨jvm虚拟机是如何加载字节码文件的。
类的生命周期经历了 加载、连接(验证、准备、解析)、初始化、使用、卸载 这7个阶段。
类加载过程
加载
这是唯一一个能由程序员自定义修改的类加载过程。其玄机在于虚拟机规范class的来源是二进制文件。关于如何获取这个二进制,程序员可控。
当类被加载完成后,会把类的相关信息存放在方法区中,然后生成一个Class
对象存放在堆内存中作为方法区的类信息的入口。
验证
验证的目的是验证这些字节码变成代码之后运行不会使虚拟机受到危害。验证并不是严格按照加载、验证、准备、解析、初始化的顺序进行的,而是穿插进行,比如在加载的时候验证、在解析的时候验证等。
验证分为很多步骤
- 验证class的格式,比如开头是魔术0xCAFEBABE等等。
- 验证元数据,也就是语义分析,检查是否符合语法。
- 字节码验证,确定合法,没有危险操作等等。
- 符号引用验证,(解析中发生的验证)验证该类是否缺少或者被禁止访问它依赖的某些外部
类、方法、字段等资源
准备
正式定义类变量(静态变量),分配内存设置类变量初始化值(0值)。
方法区是一个规范,HotSpot
在jdk8
之前都把方法区放在永久代。在jdk8
开始,没有永久代了,方法区被迁移到了堆内存中,所以jdk8
之后类变量实际上和Class
对象一样,都在堆内存中。
解析
将符号常量引用替换成直接引用。
初始化
初始化阶段就是执行类构造器<clinit>()方法的过程。
在准备阶段给所有类变量赋0值,在初始化阶段调用构造器()来初始化程序员写的值。
<clinit>()是指所有类变量赋值的块,比如静态代码块、构造器等。
jvm保证在执行子类的<clinit>()前父类的<clinit>()一定已经被执行。
类加载器
顾名思义,类加载器是用来加载类到方法区的代码,值得关注的是类加载器可以由我们去实现,也就可以由我们来决定类是如何被加载的了。
注意:任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。
双亲委派机制
站在jvm和程序员的角度看待类加载器是不一样的,jvm只认为有两种类加载器 启动类加载器(
Bootstrap ClassLoader
)和其他(java.lang.ClassLoader
)
程序员角度有三层类加载器 启动类加载器(Bootstrap Class Loader
)、扩展类加载器(Extension Class Loader
)、应用程序类加载器(Application Class Loader
)
三层类加载器
- 启动类加载器(
Bootstrap Class Loader
):加载器负责加载存放在
<JAVA_HOME>\lib目录存放的jdk代码。 - 扩展类加载器(
Extension Class Loader
):它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。(允许对JavaSE扩展) - 应用程序类加载器(
Application Class Loader
):它负责加载用户类路径
(ClassPath)上所有的类库。
双亲委派机制
双亲委派模型的工作过程是:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加
载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请
求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。