作者简介:李国。前京东高级架构师。本文选自:拉勾教育专栏《深入浅出 Java 虚拟机》?
本课时我们主要从覆盖 JDK 的类开始讲解 JVM 的类加载机制。其实,JVM 的类加载机制和 Java 的类加载机制类似,但 JVM 的类加载过程稍有些复杂。
前面课时我们讲到,JVM 通过加载 .class 文件,能够将其中的字节码解析成操作系统机器码。那这些文件是怎么加载进来的呢?又有哪些约定?接下来我们就详细介绍 JVM 的类加载机制,同时介绍三个实际的应用场景。
我们首先看几个面试题。
- 我们能够通过一定的手段,覆盖 HashMap 类的实现么?
- 有哪些地方打破了 Java 的类加载机制?
- 如何加载一个远程的 .class 文件?怎样加密 .class 文件?
关于类加载,很多同学都知道双亲委派机制,但这明显不够。面试官可能要你讲出几个能打破这个机制的例子,这个时候不要慌。上面几个问题,是我在接触的一些比较高级的面试场景中,遇到的一些问法。在平常的工作中,也有大量的相关应用,我们会理论联系实践综合分析这些问题。
类加载过程
现实中并不是说,我把一个文件修改成 .class 后缀,就能够被 JVM 识别。类的加载过程非常复杂,主要有这几个过程:加载、验证、准备、解析、初始化。这些术语很多地方都出现过,我们不需要死记硬背,而应该要了解它背后的原理和要做的事情。
如图所示。大多数情况下,类会按照图中给出的顺序进行加载。下面我们就来分别介绍下这个过程。
加载
加载的主要作用是将外部的 .class 文件,加载到 Java 的方法区内,你可以回顾一下我们在上一课时讲的内存区域图。加载阶段主要是找到并加载类的二进制数据,比如从 jar 包里或者 war 包里找到它们。
验证
肯定不能任何 .class 文件都能加载,那样太不安全了,容易受到恶意代码的攻击。验证阶段在虚拟机整个类加载过程中占了很大一部分,不符合规范的将抛出 java.lang.VerifyError 错误。像一些低版本的 JVM,是无法加载一些高版本的类库的,就是在这个阶段完成的。
准备
从这部分开始,将为一些类变量分配内存,并将其初始化为默认值。此时,实例对象还没有分配内存,所以这些动作是在方法区上进行的。
局部变量不像类变量那样存在准备阶段。类变量有两次赋初始值的过程,一次在准备阶段,赋予初始值(也可以是指定值);另外一次在初始化阶段,赋予程序员定义的值。
因此,即使程序员没有为类变量赋值也没有关系,它仍然有一个默认的初始值。但局部变量就不一样了,如果没有给它赋初始值,是不能使用的。
解析
解析在类加载中是非常非常重要的一环,是将符号引用替换为直接引用的过程。这句话非常的拗口,其实理解起来也非常的简单。
符号引用是一种定义,可以是任何字面上的含义,而直接引用就是直接指向目标的指针、相对偏移量。
直接引用的对象都存在于内存中,你可以把通讯录里的女友手机号码,类比为符号引用,把面对面和你吃饭的人,类比为直接引用。
解析阶段负责把整个类激活,串成一个可以找到彼此的网,过程不可谓不重要。那这个阶段都做了哪些工作呢?大体可以分为:
- 类或接口的解析
- 类方法解析
- 接口方法解析
- 字段解析
我们来看几个经常发生的异常,就与这个阶段有关。
- java.lang.NoSuchFieldError 根据继承关系从下往上,找不到相关字段时的报错。
- java.lang.IllegalAccessError 字段或者方法,访问权限不具备时的错误。
- java.lang.NoSuchMethodError 找不到相关方法时的错误。
解析过程保证了相互引用的完整性,把继承与组合推进到运行时。
初始化
如果前面的流程一切顺利的话,接下来该初始化成员变量了,到了这一步,才真正开始执行一些字节码。
关于执行字节码,将会在我的专栏中讲明。欢迎大家来订阅学习。
本文选自:拉勾教育专栏《深入浅出 Java 虚拟机》?
版权声明:本文版权归属拉勾教育及该专栏作者,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表,违者必究。
本文暂时没有评论,来添加一个吧(●'◡'●)