JVM之Java类加载机制
什么是类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这既是虚拟机的类加载机制
类的生命周期
生命周期简述
类从被加载到虚拟机内存开始,到卸载为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)7个阶段,其中验证、准备、解析三个部分被称为连接(Linking),这7个阶段发生的顺序如下:
其中加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类加载的过程会按照这种顺序执行,而解析阶段不一定,它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(动态绑定)
类立即初始化的5中情况:
1、遇到new、getstatic、putstatic或invokestatic这4条指令时,如果类没有进行初始化,则需要先触发其初始化。生成这4条指令的Java应用场景是:使用new关键字实例化对象时、读取或设置一个类的静态字段时、调用一个类的静态方法时。
2、使用java.lang.reflect包的方法,对类进行反射调用的时候
3、当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4、当虚拟机启动时,用户需要指定一个要执行的类(包含main方法的类),虚拟机会先初始化这个主类
5、当使用jdk动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
类加载过程说明
加载
在加载阶段,虚拟机需要完成3件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
相对于类加载过程的其它阶段,一个非数组类的加载阶段是开发人员可控性最强的,因为加载阶段既可以使用系统提供的引导类加载器来完成,也可以由用户自定义的类加载器去完成,开发人员可以通过自定义的类加载器去控制字节流的获取方式。
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致上会完成4个阶段的校验工作:文件格式校验、元数据校验、字节码校验、符号引用校验。
对于虚拟机的类加载机制来说,验证阶段是一个非常重要的、但不是一定必要(因为对程序运行期没有影响)的阶段。如果所运行的全部代码都已经被反复使用和验证过,那么在实施阶段可以考虑使用-Xverify:none参数关闭大部分的类验证,以缩短虚拟机类加载时间
文件格式校验
该阶段校验的目的是确保输入的字节流能正确地解析并存储到方法区中,格式上符合描述一个Java类型信息的要求。这阶段的验证是基于二进制字节流进行的,只有通过了这个阶段的验证后,字节流才会进入内存的方法区中进行存储,所以后面的3个校验阶段全部是基于方法区的存储结构进行的,不会再直接操作字节流
元数据校验
该阶段的主要目的是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息
字节码校验
该阶段是整个验证阶段最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型做完校验后看这个阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的时间
符号引用校验
最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外的信息进行匹配性校验。符号引用验证的目的是确保解析动作能正常运行,如果无法通过符号引用验证,那么会抛出一个java.lang.IncompatibleClassChangeError异常的子类,如java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段有两个容易混淆的概念需要注意一下:
首先、这个时候进行内存分配的仅包含类变量(被static修饰的变量),不包括实例变量,实例变量是在对象实例化时随着对象一起分配在Java堆中。
其次、这里所说的初始值通常情况下是数据类型的零值,假设一个类变量定义为:
public static int value=123;
那么变量在准备阶段的初始值为0不是123,在初始化阶段变量value的值才是123最后、特殊情况,如果类字段的字段属性表中存在ConstantValue属性,在准备阶段变量value的值会被初始化为ConstantValue属性所指定的值,将设上面的类变量定义为:
public static final int value=123;
编译时javac将会为value生成ConstantValue属性,在准备阶段变量value就会被初始化为123
解析
该阶段是把类中的符号引用转换为直接引用,解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。该解析过程又分为4种引用解析过程,类或接口解析、字段解析、类方法解析、接口方法解析
初始化
初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
[x] 声明类变量是指定初始值。
[x] 使用静态代码块为类变量指定初始值。
类什么时候才被初始化:
1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName())
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
类的初始化步骤:
1)如果这个类还没有被加载和链接,那先进行加载和链接
2)假如这个类存在直接父类,并且这个类还没有被初始化,那就初始化直接的父类(不适用于接口)
3 ) 假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
类加载器
虚拟机设计团队把类加载阶段中的通过一个类的全限定名来获取描述此类的二进制字节流的动作放到了Java虚拟机的外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为类加载器
类与类加载器
类的加载器虽然只用于实现类的加载动作,但它在java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一起确立其在Java虚拟机中的唯一性,每一个类加载器都有一个独立的类名称空间。通俗点就是:比较两个类是否相等,只有这两个类是被同一个类加载器加载的前提下,这个比较才有意义,否则,即便这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。
双亲委派模型
图中展示了类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都因该有自己的父类加载器。这里的类加载器之间的父子关系不会以继承(Inheritance)的关系来实现,而是使用组合(Composition)关系来复用父加载器的代码
启动类加载器(Bootstrap ClassLoader)
该类加载器负责将java_home\lib目录下或被-Xbootclasspath参数所指定的路径中并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,那直接使用null代替即可
扩展类加载器(Extension ClassLoader)
该加载器负责加载java_home\lib\ext目录下的或被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器
应用程序类加载器(Application ClassLoader)
该类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称他为系统类加载器。它负责加载用户类路径(ClassPath)下所指定的类库,开发者可以直接使用该类加载器
双亲委派模型的工作流程
如果一个类加载器收到了类加载的请求,它首先不会自己尝试加载这个类,而是把请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传递给顶层的启动类加载器,只有当父类反馈无法完成这个加载请求时,子加载器才会尝试自己去加载
双亲委派模型的优点
1、Java类随着类加载器具备了带有优先级的层次关系
例如:类java.lang.Object,它存放在rt.jar中,无论哪个加载器要加载这个类都要委派给启动类加载器进行加载,因此Object类在各种类加载器环境中都是同一个类。反之,如果用户自定义了一个称为java.lang.Object类,并放在了程序的ClassPath下,那系统中就会出现多个不同的Object类,应用程序将变得混乱
2、保证Java程序的稳定运行
实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中,它会先检查是否已经被加载过,若没有加载则调用父加载器loadClass()方法,如果父容器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//如果父类加载器抛出ClassNotFoundException
//说明父类加载器无法完成加载请求
}
if (c == null) {
// 父类加载器无法加载的时候
// 再调用本身的findClass()方法进行类加载
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
参考资料:深入理解Java虚拟机
JVM之Java类加载机制的更多相关文章
- jvm之java类加载机制和类加载器(ClassLoader),方法区结构,堆中实例对象结构的详解
一.类加载或类初始化:当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载.连接.初始化3个步骤来对该类进行初始化.如果没有意外,JVM将会连续完成3个步骤. 二.类加载时机: 1 ...
- Java 类加载机制
类的加载: 类的初始化: 类什么时候才被初始化:1)创建类的实例,也就是new一个对象2)访问某个类或接口的静态变量,或者对该静态变量赋值3)调用类的静态方法4)反射(Class.forName(&q ...
- Java类加载机制深度分析
转自:http://my.oschina.net/xianggao/blog/70826 参考:http://www.ibm.com/developerworks/cn/java/j-lo-class ...
- 理解Java类加载机制(译文)
理解java类加载机制 你想写类加载器?或者你遇到了ClassCastException异常,或者你遇到了奇怪的LinkageError状态约束异常.应该仔细看看java类的加载处理了. 什么是类加载 ...
- 剑指Offer——知识点储备-故障检测、性能调优与Java类加载机制
剑指Offer--知识点储备-故障检测.性能调优与Java类加载机制 故障检测.性能调优 用什么工具可以查出内存泄露 (1)MerroyAnalyzer:一个功能丰富的java堆转储文件分析工具,可以 ...
- 两道面试题,带你解析Java类加载机制
文章首发于[博客园-陈树义],点击跳转到原文<两道面试题,带你解析Java类加载机制> 在许多Java面试中,我们经常会看到关于Java类加载机制的考察,例如下面这道题: class Gr ...
- 【转】两道面试题,带你解析Java类加载机制(类初始化方法 和 对象初始化方法)
本文转自 https://www.cnblogs.com/chanshuyi/p/the_java_class_load_mechamism.html 关键语句 我们只知道有一个构造方法,但实际上Ja ...
- Java类加载机制及自定义加载器
转载:https://www.cnblogs.com/gdpuzxs/p/7044963.html Java类加载机制及自定义加载器 一:ClassLoader类加载器,主要的作用是将class文件加 ...
- Java类加载机制的理解
算上大学,尽管接触Java已经有4年时间并对基本的API算得上熟练应用,但是依旧觉得自己对于Java的特性依然是一知半解.要成为优秀的Java开发人员,需要深入了解Java平台的工作方式,其中类加载机 ...
随机推荐
- WIN7(WINDOWS7)在添加网络打印机时提示这个,这里的密码是什么密码,能不能不用密码?
360急救箱应该提高计算机的网络访问安全性,加上与验证机制,所以当你要访问的网络资源,你需要输入用户名和密码进行认证. 1,点击“开始 - 运行”,输入gpedit.msc然后按Enter键. 2,计 ...
- mvc布局(一)
negut添加Optimization @System.Web.Optimization.Styles.Render( "~/Content/styles/css/font-awesome. ...
- sequelize学习笔记
示例: const Sequelize = require('sequelize'); // 建立连接 const sequelize = new Sequelize('test', 'root', ...
- Fox新闻报道,帮助北朝鲜使用加密货币专家被捕
根据司法部的刑事诉讼,一名美国加密货币专家周四在洛杉矶被捕,原因是涉嫌帮助朝鲜使用加密货币逃避美国的制裁.网民都说敢帮助敌人,就应该关起来.
- webpack的postcss的基本应用
PostCss是什么? PostCSS在webpack中的基本应用 一.PostCss是什么? 如果有深入学习PostCss需求的话可以参考大漠的资料:https://www.w3cplus.com/ ...
- SSO系统介绍
1.什么是sso系统 SSO英文全称Single Sign On,单点登录.SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.它包括可以将这次主要的登录映射到其他应用中用于 ...
- mac下MySQL出现乱码的解决方法
之前写过一篇Linux下MySQL出现乱码的解决方法,本文说下mac下的处理,其实处理方式是一样的,我电脑的mysql版本是5.7.26-log 网上很多帖子都说去/usr/local/mysql/s ...
- STM32WB 信息块之OTP
1.OTP Area范围:0x1FFF 7000 - 0x1FFF 73FF 大小1 K 2.OTP描述 1 KB (128 double words) OTP (one-time programma ...
- CentOS下安装软件
CentOS下安装软件,要看下载的软件安装包的后缀名是什么,一般为了方便安装,推荐下载以 rpm 结尾的软件包. 比如以下截图,有多种下载方式,推荐下载圈起来的链接. rpm包安装方式步骤: 找到相应 ...
- bug的全部
BUG 的生命周期 BUG 的生命周期 Bug-->软件程序的漏洞或缺陷 Bug 的类型:代码错误.设计缺陷.界面优化.性能问题.配置相关.安装部署.安全相关.标准规划.测试脚本....其他(功 ...