Java中JVM的类加载器详解

 更新时间:2023年07月31日 11:41:42   作者:兔老大RabbitMQ  
这篇文章主要介绍了Java中的类加载器详解,从概念上来讲, 自定义类加载器一般指的是程序中由开发人员自定义的类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器,需要的朋友可以参考下

类加载过程

加载->连接->初始化。

连接过程又可分为三步:验证->准备->解析。

类加载器分类

JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader

  1. 启动类加载器(Bootstrap ClassLoader)
    此类加载器负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
  2. 扩展类加载器(Extension ClassLoader)
    这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
  3. 应用程序类加载器(Application ClassLoader)
    这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

双亲委派模型

每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 双亲委派模型 。

1、在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。

2、加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。

3、当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。

好处

使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。

例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。

由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。

保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。

如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

注意

这个双亲翻译的容易让人误解,一般理解双亲都是父母,这里的双亲表达的是“父母一辈”的人,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外,类加载器之间的“父子”关系也不是通过继承来体现的,是由“优先级”来决定。官方API文档对这部分的描述如下:

The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a "parent" class loader. When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself.

 源码分析

private final ClassLoader parent; 
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查请求的类是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //父加载器不为空,调用父加载器loadClass()方法处理
                        c = parent.loadClass(name, false);
                    } else {
                        //父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   //父类加载器无法完成加载请求
                }
                if (c == null) {
                    long t1 = System.nanoTime();
                    //自己尝试加载
                    c = findClass(name);
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

自定义加载器

除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader

如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader

示例:自定义一个NetworkClassLoader,用于加载网络上的class文件

package classloader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
/**
 * 加载网络class的ClassLoader
 */
public class NetworkClassLoader extends ClassLoader {
	private String rootUrl;
	public NetworkClassLoader(String rootUrl) {
		this.rootUrl = rootUrl;
	}
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		Class clazz = null;//this.findLoadedClass(name); // 父类已加载	
		//if (clazz == null) {	//检查该类是否已被加载过
			byte[] classData = getClassData(name);	//根据类的二进制名称,获得该class文件的字节码数组
			if (classData == null) {
				throw new ClassNotFoundException();
			}
			clazz = defineClass(name, classData, 0, classData.length);	//将class的字节码数组转换成Class类的实例
		//} 
		return clazz;
	}
	private byte[] getClassData(String name) {
		InputStream is = null;
		try {
			String path = classNameToPath(name);
			URL url = new URL(path);
			byte[] buff = new byte[1024*4];
			int len = -1;
			is = url.openStream();
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			while((len = is.read(buff)) != -1) {
				baos.write(buff,0,len);
			}
			return baos.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (is != null) {
			   try {
			      is.close();
			   } catch(IOException e) {
			      e.printStackTrace();
			   }
			}
		}
		return null;
	}
	private String classNameToPath(String name) {
		return rootUrl + "/" + name.replace(".", "/") + ".class";
	}
}

到此这篇关于Java中JVM的类加载器详解的文章就介绍到这了,更多相关JVM类加载器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java实现简单聊天软件

    java实现简单聊天软件

    这篇文章主要为大家详细介绍了java实现简单的聊天软件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • Java使用Semaphore对单接口进行限流

    Java使用Semaphore对单接口进行限流

    本篇主要讲如何使用Semaphore对单接口进行限流,主要有三种方式,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07
  • Java 中的5个代码性能提升技巧

    Java 中的5个代码性能提升技巧

    这篇文章主要给大家分享了Java的5个代码性能提升的技巧,虽然大多数情况下极致优化代码是没有必要的,但是作为一名技术开发者,我们还是想追求代码的更小、更快,更强。如果哪天发现程序的运行速度不尽人意,就需要这样的文章了,需要的朋友可以参考一下
    2021-12-12
  • java实现Base64加密解密算法

    java实现Base64加密解密算法

    Base64用来将非ASCII字符的数据转换成ASCII字符的一种方法,这篇文章主要为大家详细介绍了java实现Base64加密解密算法,感兴趣的小伙伴们可以参考一下
    2016-04-04
  • 将Java的List结构通过GSON库转换为JSON的方法示例

    将Java的List结构通过GSON库转换为JSON的方法示例

    GONS是Google在GitHub上开源的Java类库,提供各种Java对象和JSON格式对象之间的转换功能,将Java的List结构通过GSON库转换为JSON的方法示例
    2016-06-06
  • spring boot实现profiles动态切换的示例

    spring boot实现profiles动态切换的示例

    Spring Boot支持在不同的环境下使用不同的配置文件,该技术非常有利于持续集成,在构建项目的时候只需要使用不同的构建命令就可以生成不同运行环境下war包,而不需要手动切换配置文件。
    2020-10-10
  • SpringBoot读取配置优先级顺序的方法详解

    SpringBoot读取配置优先级顺序的方法详解

    Spring Boot作为一种轻量级的Java应用程序框架,以其开箱即用、快速搭建新项目的特性赢得了广大开发者的青睐,在Spring Boot生态系统中,配置属性可以从各种来源获取,本文将深入探讨Spring Boot加载外部配置属性的优先级规则,需要的朋友可以参考下
    2024-05-05
  • 详解Java中final的用法

    详解Java中final的用法

    本文主要介绍了Java中final的使用方法,final是java的关键字,本文就详细说明一下它的使用方法,需要的朋友可以参考下
    2015-08-08
  • 解决mybatis批量更新(update foreach)失败的问题

    解决mybatis批量更新(update foreach)失败的问题

    这篇文章主要介绍了解决mybatis批量更新(update foreach)失败的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • Spring Cloud Gateway实现灰度发布方案

    Spring Cloud Gateway实现灰度发布方案

    灰度发布是在微服务中的表现为同一服务同时上线不同版本,让一部分用户使用新版本来验证新特性,如果验证没有问题,则将所有用户都迁移到新版本上,本文就来介绍一下如何实现,感兴趣的可以了解一下
    2023-12-12

最新评论