java类加载过程以及双亲委派机制

前言:最近两个月公司实行了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编程的路上越走越远。本篇博客的设计的开发代码比较少,都是一些关于概念的理解。在开发过程中,我们也是不仅仅只注重写代码,修炼内功也是必不可少的一部分。

原文地址:https://www.cnblogs.com/wyq178/p/10127578.html

时间: 2024-08-14 06:09:18

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

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

前言 之前详细介绍了Java类的整个加载过程(类加载机制详解).虽然,篇幅较长,但是也不要被内容吓到了,其实每个阶段都可以用一句话来概括. 1)加载:查找并加载类的二进制字节流数据. 2)验证:保证被加载的类的正确性. 3)准备:为类的静态变量分配内存,并设置默认初始值. 4)解析:把类中的符号引用转换为直接引用. 5)初始化:为类的静态变量赋予正确的初始值. 当然,要想掌握类加载机制,还是需要去深入研究的.(好吧,说了一句正确的废话)因为其中,有很多知识点也是面试中常问的.比如,我之前去面试的

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

深入JVM系列(三)之类加载.类加载器.双亲委派机制与常见问题 http://blog.csdn.net/vernonzheng/article/details/8461380 转--深入JVM系列(三)之类加载.类加载器.双亲委派机制与常见问题

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

JVM总括四-类加载过程.双亲委派模型.对象实例化 一. 类加载过程 一定要注意每个过程执行的内容!!!!!! 1.Load: 将编译后的.class文件以二进制流的方式加载到JVM内存中,并转化为特定的数据结构,用到的就是classLoad二类加载器.这个过程中校验cafe babe魔法数.常量池.文件长度.是否有父类等. 2.Link: 分为验证.准备.解析三步. 验证:更为详细的验证,比如:final是否规范(二次赋值不规范).static是否合理(静态方法必须引用静态变量).类型是否正确

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

一.概述 定义:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型.类加载和连接的过程都是在运行期间完成的. 二. 类的加载方式 1):本地编译好的class中直接加载 2):网络加载:java.net.URLClassLoader可以加载url指定的类 3):从jar.zip等等压缩文件加载类,自动解析jar文件找到class文件去加载util类 4):从java源代码文件动态编译成为class文件 三.类加载的时机

关于Java类加载双亲委派机制的思考(附一道面试题)

预定义类加载器和双亲委派机制 JVM预定义的三种类型类加载器: 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar).由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作. 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)

java安全沙箱(一)之ClassLoader双亲委派机制

java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是: 类加载体系 .class文件检验器 内置于Java虚拟机(及语言)的安全特性 安全管理器及Java API 本篇博客主要介绍下"类加载体系",介绍下它的基本原理并分享下jdk的实现源码:其它几类安全机制会在后续博客中陆续介绍. 类加载体系简介 "类加载体系"及ClassLoader双亲委派机制.java程序中的 .java文件编译完会生成 .class文件,而

[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的

Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的 不过源码其实比较简单,接下来简单介绍一下 我们先从启动类说起 有一个Launcher类   sun.misc.Launcher; 仔细看下这简短的几行注释,可以得到有用的信息 ps:直接IDE里面查看反编译的,看不到注释的,可以下载openJDK查看源码,我的这个版本是openjdk-8-src-b132-03_mar_2014 sun.misc.Launcher这个类是系统用于

java 类加载器 &nbsp; 双亲委派机制

先上图,看一下加载器. 1.BootStrapClassLoader:启动类加载器,该ClassLoader是在启动时候创建的,是写在JVM内核里的,它不是一个字节码文件,是由c++编写的二进制代码,所以开发者无法获取到该启动类的引用,也就不能通过引用来进行操作.这个加载器是加载$JAVA_HOME/jre/lib下面的类库(或者通过参数-Xbootclasspath指定). 2.EXTClassLoader:扩展类加载器,ExtClassLoader会加载 $JAVA_HOME/jre/lib

java的类加载器体系结构和双亲委派机制

类加载器将字节码文件加载到内存中,同时在方法区中生成对应的java.land.class对象  作为外部访问方法区的入口. 类加载器的层次结构: 引导类加载器<-------------扩展类加载器<--------------------------引用程序类加载器<-----------------自定义类加载器 1.引导类加载器加载jre/lib/rt.jar包下加载核心类库 2.扩展类加载器 负责加载jre/lib/ext/*.jar 3.引用程序类加载器  加载我们自定义类 4