详解JAVA类加载器

简介: 1.概述概念:类加载器(Class Loader)是Java虚拟机(JVM)的一个重要组件,负责加载Java类到内存中并使其可以被JVM执行。类加载器是Java程序的核心机制之一。分类:类加载器一共有三种:启动类加载器,加载系统类rt.jar。扩展类加载器,加载JDK内部,rt.jar之外,由于JDK版本迭代而新出现的扩展类。应用类加载器加载J用户所配置的classpath下的类。

1.概述

概念:

类加载器(Class Loader)是Java虚拟机(JVM)的一个重要组件,负责加载Java类到内存中并使其可以被JVM执行。类加载器是Java程序的核心机制之一。


分类:


类加载器一共有三种:


启动类加载器,加载系统类rt.jar。

扩展类加载器,加载JDK内部,rt.jar之外,由于JDK版本迭代而新出现的扩展类。

应用类加载器加载J用户所配置的classpath下的类。

三种类加载器之间从上到下有父子关系,上层是下层的父加载器。

85f3a1163cff4f8ab92a9fd2d2dd8c2f.png

2.双亲委派

在类加载中可能会遇见这样一种情况:


JDK里自带一个java.lang.String,有一个恶意的攻击类也将类的全类名命名为java.lang.String。如果我们想使用String类型的时候,类加载器将这个伪装成JDK的String类的恶意类加载执行,系统就将遭受到破坏。


很明显,为了安全起见JDK需要一种合理的加载方式来控制类的加载,这种合理的加载方式就是——双亲委派机制。


双亲委派机制总结起来就是:


先找父级加载器要,要是父级加载器没有,再由自己加载。


因为启动类加载器、扩展类加载器工作在应用的启动阶段,加载的也是JDK内核相关的jar,与应用没有太大关系,与应用相关的只有应用程序加载类,而应用程序的父级最多也就是扩展类加载器、启动类加载器,也就是最多找两个双亲拿,所以称为双亲委派机制。


整个类加载的详细流程为:


我们来看看任意一个ClassLoader的loadClass源码,整个源码中双亲委派的体现很直接:

c49f88196fe44cbfab1d5b3677761af1.png

类加载器在需要加载一个类(class)的时候会先判断类是否被自己加载过,


如果没有,调用其成员变量classloader parent委托其父载器帮忙加载,


父加载器再重复以上过程,如果递归到最顶级的启动类加载器,发现都加载不了,


当前启动器会自行加载。

3.ServiceClassLoader

SPI机制:

SPI(Service Provider Interface),是在JDK1.6中提出的一种基于接口的服务发现机制,它允许第三方服务提供者扩展框架的功能。在SPI机制中,框架定义一组接口,并规定这些接口的实现类必须以一定的命名规则放在特定的路径下,然后通过Java自带的SPI机制,动态地加载和实例化这些实现类。


基于SPI机制可以实现这样的生态结构,由官方制定一些标准,厂商去给出实现,也就是官方给出接口,由各大厂商去实现接口,如JAVA EE规范中的JDBC规范等等,支撑SPI实现的核心是一个类加载器——ServiceClassLoader。

e9e09f15108641399187245f322545f0.png

或抽象类的类,而服务加载器则负责在运行时动态地查找和加载这些实现类。


定义服务接口:定义一个Java接口或抽象类,用于描述服务提供者必须实现的行为。


实现服务提供者:定义一个或多个服务提供者,实现服务接口或抽象类。


配置服务提供者:在META-INF/services目录下创建一个以服务接口或抽象类名为文件名的文件,并在文件中列出所有实现服务接口或抽象类的类名。


加载服务提供者:使用Service Loader类动态加载和实例化服务提供者。


代码示例:

接口:

package com.eryi;
public interface HelloService {
    void sayHello();
}

实现类:

package com.eryi;
public class ChineseHelloService implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("你好!");
    }
}
package com.eryi;
public class EnglishHelloService implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("Hello!");
    }
}

发布服务:

在META-INF/services目录下创建一个名为"com.eryi.HelloService"的文件,文件内容如下:

com.eryi.EnglishHelloService
com.eryi.ChineseHelloService

服务发现:

package com.eryi;
import java.util.ServiceLoader;
public class test {
    public static void main(String[] args) {
        ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
        for (HelloService service : loader) {
            service.sayHello();
        }
    }
}

运行结果:

bf3a97dac3654636ae910e71cbbd1ebe.png

4.URLClassLoader

启动类加载器、扩展类加载器、应用类加载器均是在程序工作之前完成对各个路径下所有jar包的加载的,这些路径里的jar包后续如果有什么变化,这三个类加载器是无法进行加载从而动态进行更新的。URLClassLoader就是为了实现这种动态加载而出现的。


URLClassLoader支持三种路径:


目录


jar包


网络代码示例:

public class test {
    public static void main(String[] args) throws Exception {
        // 创建一个URLClassLoader,指定加载路径为当前目录
        URLClassLoader classLoader = new URLClassLoader(new URL[] { new URL("file:./") });
        // 动态加载HelloWorld类
        Class<?> helloWorldClass = classLoader.loadClass("HelloWorld");
        // 创建HelloWorld对象
        Object helloWorld = helloWorldClass.newInstance();
        // 调用HelloWorld对象的sayHello方法
        helloWorldClass.getMethod("sayHello").invoke(helloWorld);
    }
}
class HelloWorld {
    public void sayHello() {
        System.out.println("Hello, world!");
    }
}

由于URLClassLoader具有动态加载的特性,所以很适合拿来做热部署。

5.加载冲突

同一个类被不同加载器加载,加载结果视为两个不同类。

所以在加载之前需要判断是否被加载,如果未加载,则遵循双亲委托模型,从而避免加载冲突。

代码示例:

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
    public static void main(String[] args) throws Exception {
        // 创建两个不同的URLClassLoader,分别加载同一个类
        URL[] classpath = { new File("classes/").toURI().toURL() };
        URLClassLoader classLoader1 = new URLClassLoader(classpath);
        URLClassLoader classLoader2 = new URLClassLoader(classpath);
        // 使用classLoader1加载HelloWorld类
        Class<?> helloWorldClass1 = classLoader1.loadClass("HelloWorld");
        // 使用classLoader2加载HelloWorld类
        Class<?> helloWorldClass2 = classLoader2.loadClass("HelloWorld");
        // 输出两个类是否相同
        System.out.println(helloWorldClass1 == helloWorldClass2); // false
    }
}
class HelloWorld {
    public void sayHello() {
        System.out.println("Hello, world!");
    }
}


目录
相关文章
|
3天前
|
前端开发 Java API
类加载器“如果我定义了一个类名与Java核心类类名相同,那它还能被加载吗?”
类加载器“如果我定义了一个类名与Java核心类类名相同,那它还能被加载吗?”
|
3天前
|
前端开发 Java 编译器
Java面向对象高级【类加载器】
Java面向对象高级【类加载器】
|
7月前
|
前端开发 Java
01Java基础 - 类加载器
01Java基础 - 类加载器
30 0
|
8月前
|
安全 前端开发 Java
Java 类加载器详解
在Java编程中,类加载器(Class Loader)是一个重要的概念,它负责将类加载到Java虚拟机中,使程序能够正常运行。本文将详细解释Java类加载器的工作原理、不同类型的类加载器以及如何自定义类加载器。
70 0
|
8月前
|
Java 应用服务中间件
《深入理解java虚拟机》——Tomcat类加载器体系结构
《深入理解java虚拟机》——Tomcat类加载器体系结构
|
3天前
|
安全 前端开发 Java
【Java】探究Java中的类加载器
【Java】探究Java中的类加载器
17 0
|
7月前
|
Java
【面试题精讲】JVM-类加载器-Java中的默认类加载器
【面试题精讲】JVM-类加载器-Java中的默认类加载器
|
7月前
|
存储 前端开发 安全
【Java基础增强】类加载器和反射
1.类加载器 1.1类加载器【理解】 作用 负责将.class文件(存储的物理文件)加载在到内存中
23 0
|
8月前
|
Java 数据安全/隐私保护
Java自定义类加载器的编写步骤
Java自定义类加载器的编写步骤
43 0
|
8月前
|
存储 缓存 前端开发
Java类加载器
Java类加载器
35 0
Java类加载器