Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
醒狮指南
JavaGuide
提交
1db5d974
J
JavaGuide
项目概览
醒狮指南
/
JavaGuide
与 Fork 源项目一致
从无法访问的项目Fork
通知
5
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
J
JavaGuide
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
1db5d974
编写于
5月 15, 2019
作者:
S
SnailClimb
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Create 类加载过程.md
上级
7fe9aad6
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
71 addition
and
0 deletion
+71
-0
docs/java/jvm/类加载过程.md
docs/java/jvm/类加载过程.md
+71
-0
未找到文件。
docs/java/jvm/类加载过程.md
0 → 100644
浏览文件 @
1db5d974
# 类加载过程
Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢?
系统加载 Class 类型的文件主要三步:
**加载->连接->初始化**
。连接过程又可分为三步:
**验证->准备->解析**
。
![
类加载过程
](
http://pqrlmrv7w.bkt.clouddn.com/img/2019-4/类加载过程.png
)
## 加载
类加载过程的第一步,主要完成下面3件事情:
1.
通过全类名获取定义此类的二进制字节流
2.
将字节流所代表的静态存储结构转换为方法区的运行时数据结构
3.
在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口
虚拟机规范多上面这3点并不具体,因此是非常灵活的。比如:"通过全类名获取定义此类的二进制字节流" 并没有指明具体从哪里获取、怎样获取。比如:比较常见的就是从 ZIP 包中读取(日后出现的JAR、EAR、WAR格式的基础)、其他文件生成(典型应用就是JSP)等等。
**一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。**
类加载器、双亲委派模型也是非常重要的知识点,这部分内容会在后面的文章中单独介绍到。
加载阶段和连接阶段的部分内容是交叉进行的,加载阶段尚未结束,连接阶段可能就已经开始了。
## 验证
![
验证阶段示意图
](
http://pqrlmrv7w.bkt.clouddn.com/img/2019-4/验证阶段.png
)
## 准备
**准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**
,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
1.
这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。
2.
这里所设置的初始值"通常情况"下是数据类型默认的零值(如0、0L、null、false等),比如我们定义了
`public static int value=111`
,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会复制)。特殊情况:比如给 value 变量加上了 fianl 关键字
`public static final int value=111`
,那么准备阶段 value 的值就被复制为 111。
**基本数据类型的零值:**
![
基本数据类型的零值
](
http://pqrlmrv7w.bkt.clouddn.com/img/2019-4/基本数据类型的零值.png
)
## 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
符号引用就是一组符号来描述目标,可以是任何字面量。
**直接引用**
就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方发表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
综上,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或者字段、方法在内存中的指针或者偏移量。
## 初始化
初始化是类加载的最后一步,也是真正执行类中定义的 Java 程序代码(字节码),初始化阶段是执行类构造器
`<clinit> ()`
方法的过程。
对于
`<clinit>()`
方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为
`<clinit>()`
方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。
对于初始化阶段,虚拟机严格规范了有且只有5中情况下,必须对类进行初始化:
1.
当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
2.
使用
`java.lang.reflect`
包的方法对类进行反射调用时 ,如果类没初始化,需要触发其初始化。
3.
初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
4.
当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
5.
当使用 JDK1.7 的动态动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic、的方法句柄,并且这个句柄没有初始化,则需要先触发器初始化。
**参考**
-
《深入理解Java虚拟机》
-
《实战Java虚拟机》
-
<https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html>
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录