一夜搞懂 | JVM 类加载机制
前言
本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍:
学习导图

一.为什么要学习类加载机制?
今天想跟大家唠嗑唠嗑Java的类加载机制,这是Java的一个很重要的创新点,曾经也是Java流行的重要原因之一。
Oracle当初引入这个机制是为了满足Java Applet开发的需求,JVM咬咬牙引入了Java类加载机制,后来的基于Jvm的动态部署,插件化开发包括大家热议的热修复,总之很多后来的技术都源于在JVM中引入了类加载器。
如今,类加载机制也在各个领域大放异彩,在面试中,由类加载机制所衍生出来各类面试题也层出不穷。
所以,我们要了解下类加载机制,为工作中或者是面试中实际的需要打好良好的基础。
二.核心知识点归纳
2.1 概述
Q1:JVM类加载机制定义:
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型的过程
Q2:特性
运行期类加载。即在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期完成的,从而通过牺牲一些性能开销来换取Java程序的高度灵活性
什么是运行期,什么是编译期?
- 编译期是指编译器将源代码翻译为机器能识别的代码,
Java被编译为Jvm认识的字节码文件- 运行期则是指
Java代码的运行过程
JVM运行期动态加载+动态连接->Java的动态扩展特性
2.2 类加载的过程
类从被加载到虚拟机内存中开始、到卸载出内存为止,整个生命周期包括七个阶段:
加载
验证
准备
解析
初始化
使用
卸载
其中,验证、准备、解析这3个部分统称为连接,流程如下图:

注意:
- 『加载』->『验证』->『准备』->『初始化』->『卸载』这五个阶段的顺序是确定的,而『解析』可能为了支持
Java的动态绑定会在『初始化』后才开始- 上述阶段通常都是互相交叉地混合式进行的,比如会在一个阶段执行的过程中调用、激活另外一个阶段
想要了解Java动态绑定和静态绑定区别的话,可以看下这篇文章:理解静态绑定与动态绑定
2.2.1 加载
Q1:任务
- 通过类的全限定名来获取定义此类的二进制字节流。如从
ZIP包读取、从网络中获取、通过运行时计算生成、由其他文件生成、从数据库中读取等等途径......
想要详细了解类的全限定名的知识,可以看下这篇文章:全限定名、简单名称和描述符是什么东西?
- 将该二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构,该数据存储数据结构由虚拟机实现自行定义
- 在内存中生成一个代表这个类的
java.lang.Class对象,它将作为程序访问方法区中的这些类型数据的外部接口
2.2.2 验证
- 是连接阶段的第一步,且工作量在
JVM类加载子系统中占了相当大的一部分 - 目的:为了确保
Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
由此可见,它能直接决定
JVM能否承受恶意代码的攻击,因此验证阶段很重要,但由于它对程序运行期没有影响,并不一定必要,可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。
检验过程包括下面四个阶段:
A.文件格式验证:
内容:验证字节流是否符合
Class文件格式的规范、以及是否能被当前版本的虚拟机处理目的:保证输入的字节流能正确地解析并存储于方法区之内,且格式上符合描述一个
Java类型信息的要求。只有保证二进制字节流通过了该验证后,它才会进入内存的方法区中进行存储,所以后续3个验证阶段全部是基于方法区而不是字节流了例子:
是否以魔数
0xCAFEBABE开头主次版本号是否在
JVM接受范围内索引值是否有指向不存在/不符合类型的常量
......
B.元数据验证:
内容:对字节码描述的信息进行语义分析,以保证其描述的信息符合
Java语言规范的要求目的:对类的元数据信息进行语义校验,保证不存在不符合
Java语言规范的元数据信息例子:
类是否有父类(除了
java.lang.Object之外,所有类都应有父类)父类是否继承了不允许被继承的类(
final修饰的类)如果该类不是抽象类,是否实现了其父类或接口中要求实现的所有方法
......
C.字节码验证:
是验证过程中最复杂的一个阶段
内容:对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件
目的:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
例子:
保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现“在操作数栈的数据类型中放置了
int类型的数据,使用时却按long类型来载入本地变量表中”保证任何跳转指令都不会跳转到方法体外的字节码指令上
......
D.符号引用验证:
- 内容:对类自身以外(如常量池中的各种符号引用)的信息进行匹配性校验
- 目的:确保解析动作能正常执行,如果无法通过符号引用验证,那么将会抛出一个
java.lang.IncompatibleClassChangeError异常的子类 - 注意:该验证发生在虚拟机将符号引用转化为直接引用的时候,即『解析』阶段
2.2.3 准备
Q1:任务
- 为类变量(静态变量)分配内存:因为这里的变量是由方法区分配内存的,所以仅包括类变量而不包括实例变量,后者将会在对象实例化时随着对象一起分配在
Java堆中 - 设置类变量初始值:通常情况下零值
2.2.4 解析
之前提过,解析阶段就是虚拟机将常量池内的符号引用替换为直接引用的过程
- 符号引用:以一组符号来描述所引用的目标
- 可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可
- 与虚拟机实现的内存布局无关,因为符号引用的字面量形式明确定义在
Java虚拟机规范的Class文件格式中,所以即使各种虚拟机实现的内存布局不同,但是能接受符号引用都是一致的
- 直接引用:
- 可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄
- 与虚拟机实现的内存布局相关,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不同
- 发生时间:
JVM会根据需要来判断,是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析 - 解析动作:有七类符号及其对应在常量池的七种常量类型
- 类或接口(
CONSTANT_Class_info)- 字段(
CONSTANT_Fieldref_info)- 类方法(
CONSTANT_Methodref_info)- 接口方法(
CONSTANT_InterfaceMethodref_info)- 方法类型(
CONSTANT_MethodType_info)- 方法句柄(
CONSTANT_MethodHandle_info)- 调用点限定符(
CONSTANT_InvokeDynamic_info)
举个例子,设当前代码所处的为类D,把一个从未解析过的符号引用N解析为一个类或接口C的直接引用,解析过程分三步:
- 若
C不是数组类型:JVM将会把代表N的全限定名传递给D类加载器去加载这个类C。在加载过程中,由于元数据验证、字节码验证的需要,又可能触发其他相关类的加载动作。一旦这个加载过程出现了任何异常,解析过程就宣告失败。- 若
C是数组类型且数组元素类型为对象:JVM也会按照上述规则加载数组元素类型- 若上述步骤无任何异常:此时
C在JVM中已成为一个有效的类或接口,但在解析完成前还需进行符号引用验证,来确认D是否具备对C的访问权限。如果发现不具备访问权限,将抛出java.lang.IllegalAccessError异常
Q1:字段(成员变量/域)和属性有什么区别?
- 属性,是指对象的属性,对于
JavaBean来说,是getXXX方法定义的- 字段,是成员变量
class Person{
private String mingzi; //mingzi是字段,一般来说字段和属性是相同的,但是这个例子是特例
public String getName(){ //name是属性
return mingzi:
}
public void setName(){
mingzi= "张三";
}
}
2.2.5 初始化
- 是类加载过程的最后一步,会开始真正执行类中定义的
Java代码。而之前的类加载过程中,除了在『加载』阶段用户应用程序可通过自定义类加载器参与之外,其余阶段均由虚拟机主导和控制 - 与『准备』阶段的区分:
- 准备阶段:变量赋初始零值
- 初始化阶段:根据Java程序的设定去初始化类变量和其他资源,或者说是执行类构造器
clinit的过程
clinit:由编译器自动收集类中的所有类变量(静态变量)的赋值动作和静态语句块static{}中的语句合并产生
- 是线程安全的,在多线程环境中被正确地加锁、同步
- 对于类或接口来说是非必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成
clinit- 接口与类不同的是,执行接口的
clinit不需要先执行父接口的clinit,只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的clinit
想详细了解clinit以及其与init的区别的读者,可以看下这篇文章:深入理解jvm--Java中init和clinit区别完全解析
- 在虚拟机规范中,规定了有且只有五种情况必须立即对类进行『初始化』:
- 遇到
new、getstatic、putstatic或invokestatic这4条字节码指令时- 使用
java.lang.reflect包的方法对类进行反射调用的时候- 当初始化一个类的时候,若发现其父类还未进行初始化,需先触发其父类的初始化
- 在虚拟机启动时,需指定一个要执行的主类,虚拟机会先初始化它
- 当使用
JDK1.7的动态语言支持时,若一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且这个方法句柄所对应的类未进行初始化,需先触发其初始化。
2.3 类加载器&双亲委派模型
每个类加载器,都拥有一个独立的命名空间,它不仅用于加载类,还和这个类本身一起作为在
JVM中的唯一标识。所以比较两个类是否相等,只要看它们是否由同一个类加载器加载,即使它们来源于同一个Class文件且被同一个JVM加载,只要加载它们的类加载器不同,这两个类就必定不相等
2.3.1 类加载器
从JVM的角度,可将类加载器分为两种:
- 启动类加载器
- 由
C++语言实现,是虚拟机自身的一部分- 负责加载存放在
<JAVA_HOME>\lib目录中、或被-Xbootclasspath参数所指定路径中的、且可被虚拟机识别的类库- 无法被
Java程序直接引用,如果自定义类加载器想要把加载请求委派给引导类加载器的话,可直接用null代替
- 其他类加载器:由
Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader,可被Java程序直接引用。常见几种:
扩展类加载器
A.由
sun.misc.Launcher$ExtClassLoader实现B.负责加载
<JAVA_HOME>\lib\ext目录中的、或者被java.ext.dirs系统变量所指定的路径中的所有类库应用程序类加载器
A.是默认的类加载器,是
ClassLoader#getSystemClassLoader()的返回值,故又称为系统类加载器B.由
sun.misc.Launcher$App-ClassLoader实现C.负责加载用户类路径上所指定的类库
自定义类加载器:如果以上类加载起不能满足需求,可自定义

需要注意的是:虽然数组类不通过类加载器创建而是由
JVM直接创建的,但仍与类加载器有密切关系,因为数组类的元素类型最终还要靠类加载器去创建
2.3.2 双亲委派模型
- 定义:表示类加载器之间的层次关系
- 前提:除了顶层启动类加载器外,其余类加载器都应当有自己的父类加载器,且它们之间关系一般不会以继承关系来实现,而是通过组合关系来复用父加载器的代码
- 工作过程:若一个类加载器收到了类加载的请求,它先会把这个请求委派给父类加载器,并向上传递,最终请求都传送到顶层的启动类加载器中。只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载
- 注意:不是一个强制性的约束模型,而是
Java设计者推荐给开发者的一种类加载器实现方式 - 优点:类会随着它的类加载器一起具备带有优先级的层次关系,可保证
Java程序的稳定运作;实现简单,所有实现代码都集中在java.lang.ClassLoader的loadClass()中
比如,某些类加载器要加载
java.lang.Object类,最终都会委派给最顶端的启动类加载器去加载,这样Object类在程序的各种类加载器环境中都是同一个类。相反,系统中将会出现多个不同的
Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱
三.课堂小测试
恭喜你!已经看完了前面的文章,相信你对
JVM类加载机制已经有一定深度的了解,下面,进行一下课堂小测试,验证一下自己的学习成果吧!
Q1:类加载的全过程是怎样的?
Q2:什么是双亲委派模型?
Q3:String类如何被加载的
上面问题的答案,在前文都提到过,如果还不能回答出来的话,建议回顾下前文
Q4:请你谈谈类加载过程,以Person a = new Person();为例进行说明
这道题是在牛客的暑假实习
Tencent一面的面筋上找的,附上标准答案:类的加载过程,Person person = new Person();为例进行说明
如果文章对您有一点帮助的话,希望您能点一下赞,您的点赞,是我前进的动力
本文参考链接:
- 《深入理解Java虚拟机》第3版
- 理解静态绑定与动态绑定
- 全限定名、简单名称和描述符是什么东西?
- 要点提炼| 理解JVM之类加载机制
- 深入理解jvm--Java中init和clinit区别完全解析
- 2019校招Android面试题解1.0(下篇)
- 类的加载过程,Person person = new Person();为例进行说明
- 编译器和运行期
- 腾讯 暑期实习 安卓 一面二面三面 面经分享
一夜搞懂 | JVM 类加载机制的更多相关文章
- 搞懂JVM类加载机制
有这样一道面试题: class Singleton{ private static Singleton singleton = new Singleton(); public static int v ...
- 一夜搞懂 | JVM 字节码执行引擎
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习字节码执行引擎? 代码编译的结果从本地机器码转变为字节码,是存储格式发展的一 ...
- 彻底搞懂JVM类加载器:基本概念
本文阅读时间大约9分钟. 写在前面 在Java面试中,在考察完项目经验.基础技术后,我会根据候选人的特点进行知识深度的考察,如果候选人简历上有写JVM(Java虚拟机)相关的东西,那么我常常会问一些J ...
- 一文教你读懂JVM类加载机制
Java运行程序又被称为WORA(Write Once Run Anywhere,在任何地方运行只需写入一次),意味着我们程序员小哥哥可以在任何一个系统上开发Java程序,但是却可以在所有系统上畅通运 ...
- 一夜搞懂 | JVM GC&内存分配
前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习GC&内存分配? 时代发展到现在,如今的内存动态分配与内存回收技术已经相当成 ...
- 一夜搞懂 | JVM 线程安全与锁优化
前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 学习导图 一.为什么要学习内存模型与线程? 之前我们学习了内存模型和线程,了解了 JMM 和线程,初步 ...
- 深入理解JVM虚拟机6:深入理解JVM类加载机制
深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体 ...
- JVM基础系列第7讲:JVM 类加载机制
当 Java 虚拟机将 Java 源码编译为字节码之后,虚拟机便可以将字节码读取进内存,从而进行解析.运行等整个过程,这个过程我们叫:Java 虚拟机的类加载机制.JVM 虚拟机执行 class 字节 ...
- JVM总结(四):JVM类加载机制
这一节我们来总结一下JVM类加载机制.具体目录如下: 类加载的过程 类加载过程概括 说说引用 详解类加载全过程: 加载 验证 准备 解析 初始化 虚拟机把描述类的数据从Class文件加载到内存,并对数 ...
随机推荐
- USB小白学习之路(2)端点IN/OUT互换
端点2(out)和端点6(in)的out_in互换 注:这里的out和in都是以host为标准说的,out是host的out,在设备(Cy7c68013)这里其实是输入端口:in是host的in,在设 ...
- windows下用Python把pdf文件转化为图片
依赖:PyMuPDF(pip install pymupdf) # -*- coding: utf-8 -*- """ 1.安装库 pip install pymupdf ...
- CKEditor4.7怎样实现上传图片,浏览服务器(无需ckfinder),nodejs图片管理,字体居中,图片居中(超详细)
首先是下载CKEditor,下载地址:http://ckeditor.com/download 选择里面的Customize自定义,如图 然后进入配置界面,第一个choose preset一般就选st ...
- 【每日一包0018】fecha
[github地址:https://github.com/ABCDdouyae...] fecha 比moment.js更加轻量级的时间解析和格式化包 format 用法:format(<Dat ...
- C++泛化双向链表
泛型双向链表 双向链表(doublyLinkedList.h) /******************************************************************* ...
- An incompatible version [1.1.33] of the APR based Apache Tomcat Native library is installed, while Tomcat requires version [1.2.14]
Springboot项目启动出现如下错误信息 解决办法在此地址:http://archive.apache.org/dist/tomcat/tomcat-connectors/native/1.2.1 ...
- vue_相同组件,不同url跳转不重新渲染的解决方法
最近写的这个项目,有很多下拉菜单,每个菜单会有相应的两种类型.现在产品的需求是,跳转到不同的类型 需要页面重新渲染数据 那么问题来了. 我试了好几种方法,用watch监听路由去判断,但是发现输在inp ...
- 关于javascript 的reduce方法
作为一个前端菜鸟,觉得资料比较好,特地分享一下~~ reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始缩减,最终为一个值. 你一定也和我一样看的有点 ...
- GPS轨迹发生模拟器介绍
GPS轨迹发生模拟器介绍 GPS信号模拟器能够模拟卫星信号运动轨迹,模拟GPS卫星导航系统的导航信号.GPS轨迹发生器可以模拟导航系统确定位置点如日期.时间.经度.纬度.海拔信息.速度等.GPS轨迹模 ...
- $props, $attrs,$listeners的具体使用例子
我在这使用属性重新render饿了么ui的tree: <el-tree ref="tree" icon-class="fa fa-caret-right" ...