前言:最近两个月公司实行了996上班制,加上了熬了两个通宵上线,状态很不好,头疼、牙疼,一直没有时间和精力写博客,也害怕在这样的状态下写出来的东西出错。为了不让自己荒废学习的劲头和习惯,今天周日,也打算写一篇博客,就算是为了给自己以前立的flag(每个月必须写几篇博客)的实现。那么本次博客的主题我选择了java的类加载过程的探究以及双亲委派机制模型以及它被破坏的场景,搞清楚这个对于我们理解java的类加载过程以及面试中都是很有必要的。

本篇博客的目录

一:类加载器

二:类加载的过程和阶段

三:双亲委派机制

四:双亲委派机制被破坏

正文

一:类加载器

1.1:类加载器的解释

类加载器是什么?在平时的开发过程中,我们会定义各种不同的类,这些类最终都会被类加载加载到jvm中,然后再解析字节码运行。如果非得给类加载器一个定义,那么它是这样的:通过一个类的全限定名来获取描述此类的而二进制字节流,这个动作是在java虚拟机外部实现的,实现这个动作的代码模块称为'类加载器';这句话乍听有些抽象,其实不难理解。拿现实中的栗子来比拟的话,比如我们去用电脑光驱放光碟这个过程:光碟就是我们写的类,光驱就是类加载器,只有通过光驱加载之后,光碟上的内容才会被解析,我们才能在屏幕上看到光碟上放入的内容。另外,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。这句话什么意思呢?就是说如果两个类在你写的内容是一模一样的,但是只要他们是由不同的类加载器加载的,那么这两个类就是不同的!

二:类加载过程

类加载一共分为七个过程,他们的具体的顺序是:加载->验证->准备->解析->初始化,接下来我们来一一介绍这些过程:

2.1:加载

类加载过程中,虚拟机需要完成以下三件事:

1)通过一个类的全限定名来获取定义此类的二进制字节流

(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

(3)在内存中生成一个代表此类的java.lang.class的对象,作为方法区的这个类的访问入口

对于我们第一印象可能是二进制字节流是从class文件中获取的,但是其实并不是这样。设计者在对类的字节流获取上并没有做出明确的约束。一个类的全限定名并不一定是从class文件中获取的,而有可能是从jar、war、ear、网络中、运行时(比如动态代理、反射技术)、jsp、数据库等,正是由于这样的开放式设计,所以java才能在如此多的平台上大放溢彩。换言之,如果java设定只有从class文件中获取的话,那么java的使用场景就会大受限制,比如反射技术就无法实现,jsp就无法直接从servlet中获取。当获取类的二进制字节流后,虚拟就按照虚拟机所需的格式存储在方法区之中,然后在内存中实例化一个class对象,这个对象将作为程序访问方法区的这些类型的外部入口。

2.2:验证

2.2.1:文件格式的验证

该验证阶段主要是保证输入的字节流能正确的解析并存储于方法区之内,格式上符合描述一个java类型信息的要求,主要的目的是保证输入的字节流能正确的解析并存储于方法区之内,该阶段的验证主要基于二进制字节流进行的 ,主要包含以下的验证:

①:是否以魔数开头②:主、次版本号是否早当前虚拟机的处理范围之内③:常量池的常量中是否有不被支持的常量类型

③:指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量

④:class文件中各个部分以及文件本身是否有被删除的活附加的其他信息

2.2.2:元数据的验

这个阶段主要是保证字节码描述的信息符合java语言规范,这个阶段可能包含的验证点如下:

①:这个类是否有父类 ②这个类的父类是否继承了不允许被继承的类(比如被final修饰的类)

②:如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法

③:类中的字段、方法是否与父类产生矛盾

该阶段主要是对类的元数据信息进行语义验证,保证不存在不符合java语言负担的元数据信息

2.2.3:字节码验证

①保证任意时刻的操作数栈的数据类型和指令代码序列都能配合工作,不会出现java类型的错误基本类型加载

②:控制跳转,保证跳转指令不会跳转到方法体以外的字节码指令上

③:保证方法体重的类型转换是有效的,比如在强制转换的过程中,只能将父类对象转换为子类对象,而不能将子类对象转换为父类对象。比如(Person peson =(Person)method.getObject(String inputParam)),但是无法实现(Object obj =(Object)method.getPerson(String inputParam))这就是java中的强制类型的转换过程控制发生在此时

2.2.4:符号引用的验证

①:符号引用中通过字符串描述的全限定名是否能找到对应的类

②:在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段

③:符号引用中的类、字段、方法中的访问性(private、protected、public、default)是否可以被当前类访问

这个阶段如果找不到的类会抛出java.lang.NosuchMethodError、java.lang.IllegalAcessError、java.lang.NoSuchFieldError等异常

2.3:准备

该阶段会正式为类变量分配内存并设置类变量初始值的阶段,这个阶段只会初始化类变量(静态字段)而不会初始化实例变量。比如以一个字段 public static Long value = 1235L;在实例化的过程中,初始化字段的初始值是0而不是1235L,但是注意一点:对于常量类或者枚举,会实例化对应的值:比如public static final Integer num = 45; 那么在准备阶段,会将num直接初始化为45,而不是0

2.4:解析

解析阶段是将常量池中的符号引用替换为直接引用的过程,解析动作主要是针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号进行解析.当进行字段解析的时候,首先会按照继承关系从下往上递归搜索各个接口和它的父接口,如果接口中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。如果不是java.lang.object的话,将会按照继承关系从下往上递归搜索其父类,如果查找到了与目标相匹配的字段,则返回这个字段的直接引用。

如果找不到,就会抛出java.lang.NoSuchFieldError异常,如果查找过程中会对这个字段进行权限验证,如果发现不具备这个字段的访问权限,将会抛出java.lang.IiieagalAccessError异常!

2.5:初始化

准备阶段,类变量(静态字段)已经赋值过一次系统要求的初始化值,二在初始化阶段,就开始根据代码中指定的值去初始化变量或者其他资源,初始化阶段是执行类构造器方法的过程。在初始化阶段,会通过执行类构造器<clinit>()方法的过程,cliint()方法与构造方法还不是完全相同的,它不需要显式的调用父类构造器,虚拟机会保证子类的clinit()方法在执行前,父类的clinit()方法已经执行完毕,因此在虚拟机中第一个被执行的clinit方法一定是java.lang.Object。

注意:clinit方法对于类或者接口来说都不是必须的,如果一个类没有静态语句块,有没有对变量的赋值操作,那么编译器可以不为这个类生成clinit方法

接口和类都有可能生成clinit()方法;虚拟机会保证在多线程环境下,clinit方法也只会执行一次,而不会执行多次。

三:双亲委派机制

3.1:类加载器的分类

3.1.1:启动类加载器

这个加载器主要负责将存放在<JAVA_HOME>的lib目录下的,或者被--Xbootclasspath参数所指定的路径中的,并且被虚拟机识别的(比如rt.jar).名字不符合的类库即使放在lib目录下也会被加载。

3.1.2:扩展类加载器

这个加载器主要负责加载存放在<JAVA_HOME>/lib/ext目录下的java类库,或者而被java.ext.dirs系统变量所指定的路径的所有类库,开发者可以直接使用扩展类加载器

3.1.3:应用程序加载器

这个类加载器负责加载用户类路径上所指定的类库,如果程序中没有定义过自己的类加载器,那么一般情况下这个就是程序中默认的类加载器。

3.2:双亲委派机制

指的是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,(每一个层次的类加载器都是如此)。只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己加载完成。

3.3:双亲委派机制的好处

3.3.1:java类随着它的加载器一起具备了一种带有优先层级的层次关系,维护基础类环境的稳定和高效的运转。例如类:java.lang.object,它存放在rt.jar中。如果没有双亲委派机制,那么如果程序员自定义了一个叫做java.lang.object的类,并且放在程序的classPath模型下,那么系统将会出现多个不同的object类,java最基础的行为也就无法得到保证,程序也会混乱一片。

3.3.2:双亲委派机制的实现

双亲委派机制的实现比较简单,主要的原理就是在类加载过程中,首先检查请求的类是否已经在被加载过了,如果没有就调用父类的加载器进行加载,如果父类加载器为null(不存在),就默认使用启动类加载器作为父类加载器,如果父类加载失败,就会抛出classNotFoundException类,再调用自己的findClass方法进行加载。

四:3次破坏双亲委派机制

4.1:第一次被破坏

第一次发生在jdk1.2发布之前,由于双亲委派模型在jdk1.2之后才被引入,而类加载器和抽象类java.lang.ClassLoader则在jdk1.0时代已经存在,意思就是设计这个东西出来的时候1.0的jdk无法满足双亲委派模型(当时也并没有考虑到),那么java的jdk设计者就为java.lang.classLoader添加了新的protected方法的findClass(),在1.0时代,classLoader只有一个loadClass()方法,而在1.2之后,findclass()方法的主要目的就是就是进行自身的类加载。

4.2:双亲委派模型的缺陷

双亲委派模型很好的解决了各个类加载的基础类的统一问题,但是假如基础类要回调用户的代码怎么办呢?而在JNDI(Java Naming and Directory Interface,Java命名和目录接口))服务中它的代码由启动类加载器去加载,但JNDI的目的就是对资源进行集中管理与资源,它需要会调用由独立厂商实现并部署在应用程序的classPath下的JNDI接口的提供者的代码,但是启动类加载器又不认识这些代码,因此双亲委派此刻就无法完成了。

如何解决这个问题?

线程上下文类加载器,这个类加载器可以通过java.lang.Thread类的setContextClassLoader方法进行设置,如果创建线程时未设置,它将会从父线程中继承一个。有了上下文类加载器,JNDI就可以通过父类加载器去请求子类加载器去完成类加载器的动作,这实际上已经违背了双亲委派模型的设计初衷,但是这也是无可奈何的事情。java中的涉及SPI的东西,比如JDBC、JAXB、JBI等加载工作都采用了这种方式!

4.3:代码热替换、模块热部署

为了达到java代码的热更新替换技术,OSGI模型经过一系列角逐,最终成了行业的标准。它的实现模块化部署的时候直接阿静一个程序模块(Bundle)连同类加载器一起换掉以实现代码的热部署,在OSGI下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更复杂的网状结构,在OSGI的实际加载过程中,只有开头符合双亲委派机制,其余的类查找都在平级的类加载器中进行加载。

 五:总结

  本篇博客主要介绍了类加载机制和它的加载过程,以及对双亲委派机制对于java的基础平台的重大意义,如何理解类加载机制并实现在java开发平台中类加载的过程对于我们实际的开发代码都是一门内功的修炼,只有修炼好了内功,才能在java编程的路上越走越远。本篇博客的设计的开发代码比较少,都是一些关于概念的理解。在开发过程中,我们也是不仅仅只注重写代码,修炼内功也是必不可少的一部分。

java类加载过程以及双亲委派机制的更多相关文章

  1. 【Java_基础】java类加载过程与双亲委派机制

    1.类的加载.连接和初始化 当程序使用某个类时,如果该类还未被加载到内存中,则系统会通过加载.连接.初始化三个步骤来对类进行初始化.如果没有意外,jvm将会连续完成这三个步骤,有时也把这三个步骤统称为 ...

  2. Java类加载器和双亲委派机制

    前言 之前详细介绍了Java类的整个加载过程(类加载机制详解).虽然,篇幅较长,但是也不要被内容吓到了,其实每个阶段都可以用一句话来概括. 1)加载:查找并加载类的二进制字节流数据. 2)验证:保证被 ...

  3. Java虚拟机类加载器及双亲委派机制

    所谓的类加载器(Class Loader)就是加载Java类到Java虚拟机中的,前面<面试官,不要再问我"Java虚拟机类加载机制"了>中已经介绍了具体加载class ...

  4. JVM总括四-类加载过程、双亲委派模型、对象实例化过程

    JVM总括四-类加载过程.双亲委派模型.对象实例化过程 目录:JVM总括:目录 一. 类加载过程 类加载过程就是将.class文件转化为Class对象,类实例化的过程,(User user = new ...

  5. JVM的类加载过程以及双亲委派模型详解

    JVM的类加载过程以及双亲委派模型详解 这篇文章主要介绍了JVM的类加载过程以及双亲委派模型详解,类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象 ...

  6. Java 基础 类加载器和双亲委派机制 学习笔记

    转自博客:https://blog.csdn.net/weixin_38118016/article/details/79579657 文章不是我写的,但是感觉写的挺通俗易懂的,然后防止以后丢失,就转 ...

  7. JVM之类加载器、加载过程及双亲委派机制

    JVM 的生命周期 虚拟机的启动 Java 虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实 ...

  8. 深入JVM系列(三)之类加载、类加载器、双亲委派机制与常见问题

    一.概述   定义:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型.类加载和连接的过程都是在运行期间完成的. 二. 类的 ...

  9. 【JAVA开发】浅析双亲委派机制

    双亲委派机制存在的意义 双亲委派只是一种说法,个人觉得叫单亲委派更合适,因为向上传递的父类只有一个,估计只是翻译过来的,形成的一种习惯,大家可以当做单亲委派 四种加载器都是用于类的加载,只是加载的对象 ...

随机推荐

  1. linux部署MantisBT(一)部署apache

    一.部署apache 1.下载apache安装包及依赖包 http://httpd.apache.org/download.cgi#apache24(apache2)http://apr.apache ...

  2. 《More Effective C++ 》读书笔记(二)Exception 异常

    这事篇读书笔记,只记录自己的理解和总结,一般情况不对其举例子具体说明,因为那正是书本身做的事情,我的笔记作为梳理和复习之用,划重点.我推荐学C++的人都好好读一遍Effective C++ 系列,真是 ...

  3. Your funds transfer has been delayed

    Hello from Amazon. Your funds transfer in the amount of 9,422.88 USD has been delayed because the cr ...

  4. tomcat开发环境配置

    1.环境配置教程 环境变量.安装版.配置版 2.编写启动tomcat的批处理文件 3.改变端口 4.虚拟目录

  5. 团队Alpha冲刺(四)

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:丹丹 组员7:何家伟 组员8:政演 组员9:鸿杰 组员10:刘一好 组员:何宇恒 展示组内最新 ...

  6. 测试bug

    模板在运行时出现了以下 1 个错误:---------------------------Controller.tt(-1,-1) : error : 获取 AppDomain 以便从主机运行转换时出 ...

  7. alpha冲刺(事后诸葛亮)

    [设想和目标] Q1:我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? "小葵日记"是为了解决18-30岁年轻用户在记录生活时希望得到一美体验友好 ...

  8. http://deepdish.io/2015/04/28/creating-lmdb-in-python/

    http://deepdish.io/2015/04/28/creating-lmdb-in-python/

  9. VC++调试基础

    一.调试基础 调试快捷键 F5:  开始调试 Shift+F5: 停止调试 F10:   调试到下一句,这里是单步跟踪 F11:   调试到下一句,跟进函数内部 Shift+F11:  从当前函数中跳 ...

  10. ltnmp 3.0 发布,PHP 开发环境一键安装包

    PHP 开发环境一键安装包, 有个叫lnmp.这个ltnmp看起来更新比较多,开发比较频繁,包括的组件更多. 安装和使用教程:http://www.moqifei.com/ltnmp 标记一下.