JAVA类加载,双亲委派原则

上一篇博客我介绍了JVM的运行时数据区中的各个角色,以及各个角色的作用,本文介绍java类加载的过程。双亲委派原则

什么是类加载: 将.java的文件编译成的.class之后,将.class文件中的二进制数据读入到内存中,将其放在运行时数据区的元数据区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在元数据区内的数据结构。

类加载器何时启动: 可以是饿汉式(只要有其它类引用了它就加载)加载类,也可以是懒加载(等到类初始化发生的时候才加载)。JVM规范允许预加载(预料某类将要被使用提前加载),若加载时存在错误则在该类首次被调用报告错误,如果该类一直没有被调用则一直不会报错。

.class文件加载的位置:

  • 从本地系统中直接加载
  • 通过网络下载.class文件
  • 从zip,jar等归档文件中加载.class文件
  • 从专有数据库中提取.class文件
  • 将Java源文件动态编译为.class文件

类的声明周期和加载流程:

在这里插入图片描述从上图可以看出类加载分7小步,3大步(加载–>连接–>初始化)。接下来根据三大步为主题介绍,详细7小步。

一:加载 :Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 认可的数据结构(Class 对象)

  • 加载: 通过类的全限定名获取二进制字节流,将其静态存储结构转换成方法区的运行时数据区结构在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

相对于类生命周期的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。

二:连接 :核心步骤,将原始的类定义信息平滑地转入 JVM 运行的过程中

  • 验证:确保被加载类的准确性,
  • 确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段是非常重要的,非必须的。可以采用-Xverify:none参数来关闭大部分的类验证措施。前提是你能够保证不做验证没问题

文件格式验证:验证字节流是否符合Class文件格式的规范,如:是否以模数0xCAFEBABE开头、主次版本号是否在当前虚拟机处理范围内等等。
元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求;如:这个类是否有父类,是否实现了父类的抽象方法,是否重写了父类的final方法,是否继承了被final修饰的类等等。
字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的,如:操作数栈的数据类型与指令代码序列能配合工作,保证方法中的类型转换有效等等。
符号引用验证:确保解析动作能正确执行;如:通过符合引用能找到对应的类和方法,符号引用中类、属性、方法的访问性是否能被当前类访问等等。

  • 准备:为类的静态变量分配内存,并将其赋默认值

为类变量分配内存并设置类变量初始值,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
只对static修饰的静态变量进行内存分配、赋默认值(如0、0L、null、false等)。
对final的静态字面值常量直接赋初值(赋初值不是赋默认值,如果不是字面值静态常量,那么会和静态变量一样赋默认值)

  • 解析:将常量池中的符号引用替换为直接引用(内存地址)的过程
  • 符号引用就是一组符号来描述目标,可以是任何字面量。属于编译原理方面的概念如:包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

    直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。如指向方法区某个类的一个指针。

    假设:一个类有一个静态变量,该静态变量是一个自定义的类型,那么经过解析后,该静态变量将是一个指针,指向该类在方法区的内存地址。

初始化: 为类的静态变量赋初值

定义静态变量时指定初始值。如 private static String str=“a”;
在静态代码块里为静态变量赋值。如 static{ str=“b”; }
*** 只有对类的主动使用才会导致类的初始化。

JVM初始化步骤
1、假如这个类还没有被加载和连接,则程序先加载并连接该类
2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
3、假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
1.创建类的实例,也就是new的方式
2.访问某个类或接口的静态变量,或者对该静态变量赋值
3.调用类的静态方法
4.反射(如 Class.forName(“com.shengsiyuan.Test”))
5.初始化某个类的子类,则其父类也会被初始化
6.Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类

父类型的初始化逻辑优先于当前类型的逻辑

卸载

  • 执行了System.exit()方法

  • 程序正常执行结束

  • 程序在执行过程中遇到了异常或错误而异常终止

  • 由于操作系统出现错误而导致Java虚拟机进程终止

什么是双亲委派?
当某个类加载器需要加载某个.class文件的时候,他会先把这个任务委托给他的上级类加载器,当他的上级类加载器没有加载,自己才回去加载这个.class文件

类加载的类别说明

  • BootstrapClassLoader:启动类加载器,加载java核心库,涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

  • ExtClassLoader:拓展类加载器,加载扩展库,如classpath中的jre ,javax.*或者
    java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器

  • AppClassLoader:系统类加载器,加载程序所在的目录,如user.dir所在的位置的class

  • CustomClassLoader:自定义类加载器,可以加载指定的class文件

类加载器流程图:
在这里插入图片描述
图上文字描述:

  1. HelloWorld.class加载开始请求AppClassLoader
  2. AppClassLoader收到加载请求,已经执行过不在执行,若没有执行过则委派父类ExtClassLoader完成加载
  3. ExtClassLoader收到加载请求,已经执行过不在执行,若没有执行过则委派父类BootStrap完成加载
  4. 如果BootStrapClassLoader加载失败,让ExtClassLoader尝试加载
  5. 如果ExtClassLoader加载失败,让AppClassLoader加载
  6. 如果AppClassLoader加载失败,让自定义的ClassLoader加载
  7. 以上加载器全部加载失败,抛出异常 ClassNotFoundException

为什么要设计这种机制

  • 可以避免重复加载,父类已经加载了,子类就不需要再次加载更加安全,很好的解决了各个类加载器的基础类的统一问题,如果不使用该种方式,那么用户可以随意定义类加载器来加载核心api,会带来相关隐患。

  • 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

  • 这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,但是在这种机制下这些系统的类已经被Bootstrap classLoader加载过了,所以并不会再去加载,从一定程度上防止了危险代码的植入。

怎么打破双亲委派机制

自定义类加载器,重写loadClass方法;
使用线程上下文类加载器;
自己的类路径下的对象走我自己的classLoader, 其他的类还是走双亲委派

为什么要破坏?不破坏行不行?

每个webapp有自己的目录和类库,比如一个webapp使用类库A1.0版本,一个webapp使用类库A2.0版本,父类加载器加载类库A1.0版本,如果使用双亲委派,会由commonClassLoader去加载类库A1.0版本,这样第二个webapp会有问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java类加载器采用双亲委派机制,这是Java安全模型的重要组成部分。这种机制保证Java核心API不会被随意篡改,同时也保证了Java程序的稳定性和安全性。 双亲委派机制的基本原则是:当一个类加载器接收到类加载请求时,它首先将该请求委派给它的父类加载器去完成,直到最顶层的父类加载器。只有在父类加载器无法完成类加载任务时,才由子类加载器自行加载。这种机制确保了Java核心API的安全性,因为只有Bootstrap ClassLoader能够加载Java核心API,其他类加载器都无法篡改这些类。 在双亲委派机制中,每个类加载器都有一个父类加载器。如果一个类加载器需要加载某个类,它会先委托给它的父类加载器去加载。如果父类加载器无法加载该类,才会由该类加载器自己去加载。这样一来,如果一个类已经被加载了,那么其类加载器的父类加载器肯定已经加载了该类,因此不会重复加载,也就避免了类的重复加载双亲委派机制的实现是通过ClassLoader类的loadClass()方法实现的。这个方法首先检查是否已经加载了该类,如果已经加载了就直接返回,否则就委托给父类加载器去加载。如果父类加载器无法加载该类,就调用findClass()方法自己加载。这样一来,每个类加载器都只需要实现自己的findClass()方法,而loadClass()方法则由ClassLoader类统一实现,从而实现了双亲委派机制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值