深入理解JVM的类加载
前言:
前面又说到Java程序实际上是将。class文件放入JVM中运行。虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是JVM的类加载机制
一、类加载的过程
类从加载虚拟机内存中开始到卸载出内存为止,生命周期包括:加载、验证、准备、解析、初始化、使用、卸载。

1、加载
通过一个类的全限定名来获取定义此类的二进制字节流(没有指明二进制字节流要从一个Class文件中获取,可以从ZIP包中读取,从网络中获取,运行时计算生成等等)将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口完成后,虚拟机外部的二进制字节流就按照虚拟机所需格式储存在方法区中。这里稍微理解一下对象和类的概念,对象是实例化的类。类的信息是存储在方法区中的,对象是存储在Java堆中的。类是对象的模板,对象是类的实例。这里主要讲类。
2、验证
加载阶段未完成,连接阶段已经开始。两者之间会交叉运行。因为Class文件可以用任何途径产生,字节流不符合Class文件格式的约束,虚拟机会抛出java.lang.VerifyError异常,所以为了保护虚拟机,JVM会有以下四个方面的验证
文件格式验证:即验证类文件结构
元数据验证:这个是否有父类,父类是否继承了不允许被继承的类等等
字节码验证:对类的方法体进行校验,JDK1.6后只需检查StackMapTable属性中的记录是否合法,JDK1.7后对于主版本号大于50的Class文件,使用类型检查来完成数据流分析
符号引用验证:全限定名是否能找到对应的类,在指定类中是否存在符合方法的字段描述以及简单名称描述的方法,字段。访问性是否正确。验证不成功会抛出java.lang.incompatibleClassChangeError异常的子类。
3、准备
正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个时候进行内存分配的只包括类变量(被static修饰的变量),并不包括实例变量,实例变量是在对象实例化时随对象一起分配在java堆中。这时候分配的初始值为零值,假设一个变量定义为:public static int value = 123;则设置变量的初始值应该为0, 而不是123。 把value赋值为123的putstatic指令是在程序被编译后,存放于类构造器<clinit>()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。如果字段属性表中存在ConstantValue属性,那么在准备阶段会将value赋值为ConstantValue属性所指定的值 。例如:public static final int value = 123; 那么在准备阶段,则会将value赋值为123;
4、解析
将符号引用转换为直接引用的过程。符号引用是一组以符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用是能无歧义的定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。而直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。
解析动作主要针对:类或接口、字段(类成员变量)、类方法、接口方法等引用进行。
类或接口的解析:判断所要转化成的直接引用是对数组类型,还是对普通的对象类型的引用,从而进行不同的解析。
字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束,查找流程如下图所示:

最后需要注意:理论上是按照上述顺序进行搜索解析,但在实际应用中,虚拟机的编译器实现可能要比上述规范要求的更严格一些。如果有一个同名字段同时出现在该类的接口和父类中,或同时在自己或父类的接口中出现,编译器可能会拒绝编译。
类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。
接口方法解析:与类方法解析步骤类似,由于接口不会有父类,因此,只递归向上搜索父接口就行了。
5、初始化
是类加载过程的最后一步,到了此阶段,才真正开始执行类中定义的Java程序代码。在准备阶段,类变量已经被赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序指定的主观计划去初始化类变量和其他资源。到初始化阶段,才真正开始执行类中的Java程序代码。即初始化阶段是执行类构造器<clinit>()方法的过程。
<clinit>()方法解析过程:<clinit>方法是有编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器的顺序是由语句在源文件中出现的顺序决定的,所以静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但不能访问。虚拟机会保证在子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行完毕,因此在虚拟机中的一个被执行的<clinit>()方法的类肯定是java。lang。Object。<clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。所以这个<clinit>()方法主要是给静态变量赋值和执行静态语句块。接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法,但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。如果在一个类的<clinit>()方法中有耗时很长的操作,就可能会造成多个进程阻塞,在实际应用中这种阻塞往往是很隐蔽的。
小结:
目前来说也就是加载阶段获取类的信息,验证阶段验证类是否符合JVM的标准。这两个阶段可以交叉运行。然后就需要在准备阶段给类分配内存空间,设置常量的初始值,初始化静态变量等等。解析阶段就是将符号引用转换为直接引用,让类在JVM上有对应的内存。最后是初始化阶段就是执行类中的静态变量赋值语句和静态语句块。到这里的类一些基础的工作已经做好了,可以使用了。
二、Java中的类加载器与双亲委派机制
实现“通过一个类的权限定名来获取描述此类的二进制字节流”这个动作的代码模块称为类加载器java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制。JVM中用来完成上述功能的具体实现就是类加载器。类加载器读取。class字节码文件将其转换成java。lang。Class类的一个实例。每个实例用来表示一个java类。通过该实例的newInstance()方法可以创建出一个该类的对象。
1、引导类加载器(Bootstrap ClassLoader)
这个类加载器负责将<JAVA_HOME>\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的。是虚拟机自身的一部分
2、扩展类加载器(Extendsion ClassLoader)
这个类加载器负责加载<JAVA_HOME>\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器。
3、应用程序类加载器(Application ClassLoader)
这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器。一般情况下这就是系统默认的类加载器
4、自定义的类加载器
这个加载器可以满足我们加载类的特殊需求,需要继承java.lang.ClassLoader类并且覆盖其中的findClass()方法和defineClass()方法。
5、双亲委派机制
上面的4个加载器并不是并行加载的,JVM中是通过双亲委派机制来加载的:

双亲委派模型是一种组织类加载器之间关系的一种规范,它的工作原理是:如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,这样层层递进,最终所有的加载请求都被传到最顶层的启动类加载器中,只有当父类加载器无法完成这个加载请求(它的搜索范围内没有找到所需的类)时,才会交给子类加载器去尝试加载。这样的好处是:java类随着它的类加载器一起具备了带有优先级的层次关系。这是十分必要的,比如java.lang.Object,它存放在\jre\lib\rt。jar中,它是所有java类的父类,因此无论哪个类加载都要加载这个类,最终所有的加载请求都汇总到顶层的启动类加载器中。Object类会由启动类加载器来加载,所以加载的都是同一个类,如果不使用双亲委派模型,由各个类加载器自行去加载的话,系统中就会出现不止一个Object类,应用程序就会全乱了。
三、必须对类进行初始化的5种情况
1、遇到new,getstatic,putstatic,invokestatic字节码指令。最常见的Java代码场景是:使用new实例化对象、读取或设置一个类的静态字段(被final修饰或已在编译器把结果放入常量池的静态字段除外)、调用一个类的静态方法
2、使用java.lang.reflect包的方法对类进行反射调用
3、类继承了父类,父类要先初始化
4、虚拟机启动,初始化主类
5、当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
深入理解JVM的类加载的更多相关文章
- 【深入理解JVM】类加载器与双亲委派模型 (转)
出处: [深入理解JVM]类加载器与双亲委派模型 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段“加载”过程中,需要通过 ...
- 深入理解JVM之类加载
---title: [学习]深入理解JVM之类加载.mddate: 2019-10-20 22:20:06tags: JVM 类加载--- Java类的加载,连接,初始化都是在程序运行期间执行的 ## ...
- 【深入理解JVM】类加载器与双亲委派模型
原文链接:http://blog.csdn.net/u011080472/article/details/51332866,http://www.cnblogs.com/lanxuezaipiao/p ...
- 深入理解JVM(3)——类加载机制
1.类加载时机 类的整个生命周期包括了:加载( Loading ).验证( Verification ).准备( Preparation ).解析( Resolution ).初始化( Initial ...
- 深入理解JVM一类加载器原理
我们知道我们编写的java代码,会经过编译器编译成字节码文件(class文件),再把字节码文件装载到JVM中,映射到各个内存区域中,我们的程序就可以在内存中运行了.那么字节码文件是怎样装载到JVM中的 ...
- 深入理解JVM - 虚拟机类加载机制 - 第七章
类加载的时机类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括了:加载/验证/准备/解析/初始化/使用/卸载七个阶段.其中验证/准备和解析统称为连接(Linking). 加载.验证.准 ...
- 深入理解JVM,类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流(即字节码)”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这个动作的代码模块称 ...
- 理解JVM之类加载机制
类完整的生命周期包括加载,验证,准备,解析,初始化,使用,卸载,七个阶段.其中验证,准备,解析统称为连接,类的卸载在前面的关于垃圾回收的博文中已经介绍. 加载,验证,准备,初始化,卸载这五个阶段的顺序 ...
- Java工程师学习指南第6部分:深入理解JVM虚拟机
本文整理了微信公众号[Java技术江湖]发表和转载过的JVM虚拟机相关优质文章,想看到更多Java技术文章,就赶紧关注本公众号吧吧. JVM原理分析,看了都说好 JVM 深入学习:Java 解析 Cl ...
随机推荐
- 验证控件 .net
检查Page.IsValid if (typeof (Page_ClientValidate) == 'function') { Page_ClientValidat ...
- Java基础:hashCode与equals个人学习记录
摘要: 本文主要记录本人对hashCode和对equals两个知识点的学习过程. 从学生时期初学java,就知道hashCode和equals这两个方法,工作中equals方法使用也是特别频繁,要说e ...
- 关于for 循环里 线程执行顺序问题
最近在做项目时遇到了 这样的需求 要在一个for循环里执行下载的操作, 而且要等 下载完每个 再去接着走循环.上网查了一些 觉得说的不是很明确.现在把我用到的代码 贴上 希望可以帮到有此需求的开发者 ...
- BZOJ 1206 [HNOI2005]虚拟内存:模拟
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1206 题意: 内存大小为n(外存无限大),共有m次访问,每一次访问的信息编号为p. 对于每 ...
- 分享知识-快乐自己:SpringMvc中的单多文件上传及文件下载
摘要:SpringMvc中的单多文件上传及文件下载:(以下是核心代码(拿过去直接能用)不谢) <!--设置文件上传需要的jar--> <dependency> <grou ...
- C++(一)— stringstream的用法
输入输出的头文件 <iostream> string流的头文件 <sstream> 文件流的头文件 <fstream> 1.利用输入输出做数据转换 stri ...
- Queue Explorer过期处理
Queue Explorer是收费软件,用一段时间后会显示过期界面无法使用, 我们可以删除注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Cogin\Queue ...
- BJOI2018爆零记
没啥可说的 Day1 0分 T1 给你一个二进制串,每次修改一个位置,询问[l,r]区间中有多少二进制子串重排后能被3整除 T2 一个无向图(无重边自环)每个点有一个包含两种颜色的染色集合,一个边的两 ...
- kettle导数删除并插入更新数据_20161130
这里有3个表 仅是时间维度不同 天 周 月,现在需要把昨天数据每天添加进入这3个表 由于业务上会有退货等情况,因此需要先把这些表原来的部分数据删除 再从那个时间点进行更新. 天需要先删除前7天的数据, ...
- codevs 1576最长严格上升子序列
传送门 1576 最长严格上升子序列 时间限制: 1 s 空间限制: 256000 KB 题目等级 : 黄金 Gold 题目描述 Description 给一个数组a1, a2 ... an ...