JVM类加载机制—类加载器和双亲委派机制详解

2024-08-25 07:12

本文主要是介绍JVM类加载机制—类加载器和双亲委派机制详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、概述

上篇我们介绍了JVM类加载机制—JVM类加载过程,类加载过程是类加载机制第一阶段,这一阶段主要做将类的字节码(class文件)加载JVM内存模型中,并转换为JVM内部的数据结构(如java.lang.Class实例),便于执行。其中类加载器是JVM用于加载类文件的一个子系统,主要是通过类的全限定名来定位和加载类的二进制文件。

二、类加载器

1、类加载器分类

JVM支持两种类型的类加载器,分别为JDK自带类加载器自定义类加载器。其中自带类加载器包括引导类加载器(启动类加载器/根类加载器)、扩展类加载器、应用程序类加载器。

引导类加载器(Bootstrap ClassLoader):也加启动类加载器或根类加载器。负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等。不继承java.lang.ClassLoader,是扩展类加载器的父加载器(不是父类)。底层有C++实现,在java代码中无法直接获取到引用,返回null。

扩展类加载器(Extension ClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包,是应用程序类加载器的父加载器。由java代码编写,继承ClassLoader类。

应用程序类加载器(Application ClassLoader):也称为系统类加载器,负责加载ClassPath路径下的类包,主要就是加载自己写的那些类。由java代码编写,继承ClassLoader类,为程序中默认的类加载器。

自定义类加载器(User-Defined ClassLoader):负责加载用户自定义路径下的类包,支持一些个性化的功能。自定义类加载器只需继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空 方法,所以我们自定义类加载器主要是重写findClass方法。代码如下

import java.io.FileInputStream;/*** 自定义类加载器*/
public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}//根据类名从磁盘上将类文件 读取到byte[]数组中private byte[] loadByte(String className) throws Exception {className = className.replace("\\.", "/");FileInputStream fis = new FileInputStream(classPath + "/" + className + ".class");int length = fis.available();byte[] bytes = new byte[length];fis.read(bytes);fis.close();return bytes;}//根据类名 加载Class类protected Class<?> findClass(String name) throws ClassNotFoundException {byte[] bytes= null;try {bytes = loadByte(name);//defineClass方法将一个字节数组转换为Class对象,这个字节数组是class文件读取后最终的字节数组return defineClass(name, bytes, 0, bytes.length);} catch (Exception e) {throw new ClassNotFoundException();}}}
}

引导类加载器是扩展类加载器的父加载器,扩展类加载器是应用程序类加载器的父加载器,但三者之间没有继承或实现的关系。

通过下面示例可以测试下类所使用的具体类加载器

/*** 类加载器过程*/
public class TestJDKClassLoader {public static void main(String[] args) {//核心程序类 支撑JVM运行 位于lib目录下  引导类加载器System.out.println("引导类加载器:" +String.class.getClassLoader());//扩展程序类 支撑JVM运行 位于lib目录中ext下的包 扩展类加载器System.out.println("扩展类加载器:" +DESKeyFactory.class.getClassLoader());//自己编写的程序 运行自身业务流程  应用程序类加载器System.out.println("应用程序类加载器:" +TestJDKClassLoader.class.getClassLoader());}
}

运行结果

通过上面结果我们可以看出 引导类加载器底层由于是C++实现,在java中无法获取,而扩展类加载器和应用程序类加载器 都是sun.misc.Launcher类下的内部类。

2、类加载初始化过程

通过类加载全过程图(加载过程)知道,类加载会创建JVM的sun.misc.Launcher类实例的启动器。sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个 sun.misc.Launcher实例。通过Launcher类的getLauncher()方法来获取类自己的加载器ClassLoader。

getLauncher()源码

public class Launcher {private static URLStreamHandlerFactory factory = new Factory();//单例模式设计,保证一个JVM中只有一个Launcher实例private static Launcher launcher = new Launcher();private static String bootClassPath = System.getProperty("sun.boot.class.path");private ClassLoader loader;private static URLStreamHandler fileHandler;//获取Launcher类实例public static Launcher getLauncher() {return launcher;}//Launcher类构造方法public Launcher() {ExtClassLoader var1;try {//构造扩展类加载器,在构造的过程中将其父加载器设置为nullvar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {//构造应用程序类加载器,在构造过程中将其父加载器设置为ExtClassLoader//Launcher类的loader属性只是AppClassLoader,一般都是用这个类加载器来加载我们写的代码this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);String var2 = System.getProperty("java.security.manager");//。。。。。省略一部分不关注的代码}
}

三、JVM类加载机制种类

全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入。


双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。


缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

四、双亲委派机制

JVM为了避免类的重复加载,确保一个类的全局唯一性以及保护程序安全,防止核心API被随意篡改。JVM会采用双亲委派模型进行加载,双亲委派模型要求除了引导类加载器外,其余的类加载器都应当有自己的父加载器。

1、工作原理

当类加载器收到加载一个类的请求时,它首先不会尝试自己去加载这个类,而是为先委托父加载器寻找这个类,找不到就会在委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。简单来说先找父加载器加载,不行再由子加载器自己加载。加载流程如下:

我们来看下应用程序类加载器APPClassLoader加载类的双亲委派机制的源码。APPClassLoader加载器的loadClass方法最终会调用其父类ClassLoader的loadClass方法。该方法大体逻辑如下:

1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接 返回。

2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false));或者是调用bootstrap类加载器来加载。

3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载。

ClassLoader.loadClass()源码

//ClassLoader的loadClass方法,里面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded//首先,检查当前类加载器起是否已经加载了该类,若当前加载器集合中有该类 直接返回//第一次加载该类 肯定为空Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//如果当前类加载器的父加载器不为空,则委托父加载器加载该类//AppClassLoader 父加载器为ExtClassLoader,直接执行还是到这个loadClass方法ExtClassLoader加载器的父加载器为null(引导类加载器是C++实现,所以为null)if (parent != null) {c = parent.loadClass(name, false);} else {//如果当前加载器的父加载器为空,则委托引导类加载器加载该类c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}//当该类通过类加载器加载为空时,通过findClass方法加载类//ExtClassLoader无findClass方法,通过父类URLClassLoader.findClass方法加载类if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//通过父类URLClassLoader.findClass方法加载类c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

URLClassLoader.findClass方法源码

//类加载器通过findClass方法尝试加载类
protected Class<?> findClass(final String name)throws ClassNotFoundException{final Class<?> result;try {result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {public Class<?> run() throws ClassNotFoundException {//通过类的name 获取类的路径//如com.test.jvm.Math 获取path = com/text/jvm/Math.classString path = name.replace('.', '/').concat(".class");//通过类路径从磁盘上拿到类文件Resource res = ucp.getResource(path, false);if (res != null) {try {//通过defineClass方法将类装载到JVM中//ExtClassLoader加载器是装载失败的,因为ExtClassLoader加载的是ext包下的路径,返回为null//再会执行AppClassLoader加载器的defineClass进行加载,才会装载JVM成功return defineClass(name, res);} catch (IOException e) {throw new ClassNotFoundException(name, e);}} else {return null;}}}, acc);} catch (java.security.PrivilegedActionException pae) {throw (ClassNotFoundException) pae.getException();}if (result == null) {throw new ClassNotFoundException(name);}return result;}

2、双亲委派机制意义

沙箱安全机制:确保一个类的全局唯一性以及保护程序安全,防止核心API被随意篡改。如自己写的java.lang.String.class类是不会被加载的

类的唯一性:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,避免重复加载,保证被加载类的唯一性

上一篇: JVM类加载机制—类加载过程

这篇关于JVM类加载机制—类加载器和双亲委派机制详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1104892

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听