Java的类加载器

JAVA类加载器

1.简介

Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。

1.1类加载的五个过程
1) 加载:根据查找路径找到相应的class文件,然后导入。类的加载方式分为
隐式加载

隐式加载指的是程序在使用new关键词创建对象时,会隐式的调用类的加载器把对应的类加载到jvm中。

显示加载

显示加载指的是通过直接调用class.forName()方法来把所需的类加载到jvm中。

2) 检查:检查夹加载的class文件的正确性。
3) 准备;给类中的静态变量分配内存空间。

(不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中。)

4) 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。
5) 初始化:对静态变量和静态代码块执行初始化工作。

在这里插入图片描述

类加载器的作用

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法
区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class
对象,作为方法区类数据的访问入口。
在这里插入图片描述

3.类加载的动态性体现

一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现

4.类加载器的层次结构

JVM中有3个默认的类加载器

1. 引导/启动(Bootstrp)类加载器

由原生代码(如C语言)编写,不继承自java.lang.ClassLoader。
它是在Java虚拟机启动后初始化的,负责加载核心Java库,加载<JAVA_HOME>/jre/lib目录下的类(比如rt.jar)

2.扩展(Extensions)类加载器

该类由sun.misc.Launcher$ExtClassLoader实现。
主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。

3.应用程序(Application )类加载器

由sun.misc.Launcher$AppClassLoader实现
它根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。
一般来说,Java 应用的类都是由它来完成加载的。

4.自定义类加载器

原因

因为Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,
比如:我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。

自定义类加载器的实现步骤

1、继承java.lang.ClassLoader
2、重写父类的findClass方法
(JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。如没有特殊的要求,一般不建议重写loadClass搜索类的算法)

三者的关系(伦理上是父子关系,但是是组合的方式实现的)

在这里插入图片描述

使用三个类加载器的原因,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型。

5.类加载器之间是如何协调工作的

java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。 在这里java采用了双亲委托机制,

双亲委托机制(代理模式的一种)

这个机制简单来讲就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

例子
public class Test {
   
   public static void main(String[] args) {
   	
   	ClassLoader c = Test.class.getClassLoader(); // 获取Test类的类加载器

   	System.out.println(c);

   	ClassLoader c1 = c.getParent(); // 获取c这个类加载器的父类加载器

   	System.out.println(c1);

   	ClassLoader c2 = c1.getParent();// 获取c1这个类加载器的父类加载器

   	System.out.println(c2);
   }
}
结果
sun.misc.Launcher$AppClassLoader@6d06d69c
sun.misc.Launcher$ExtClassLoader@70dea4e
null

可以看出Test是由AppClassLoader加载器加载的,
AppClassLoader的Parent 加载器是 ExtClassLoader,
ExtClassLoader的Parent为 null

原因是前面有提到Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java程序代码里试图打印出其内容时,我们就会看到输出为null。

6.为什么要使用这种双亲委托模式

因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。

为了保证 Java 核心库的类型安全。
• 这种机制就保证不会出现用户自己能定义java.lang.Object类的情况

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值