Linux系统之----进程控制

1.进程创建

进程创建部分由于就是fork函数,还有写时拷贝,在上一篇已经讲述过了,这里不在进行赘述,有疑问的读者可以前往上一篇博文《Linux系统--程序地址空间》中阅读!

这里在多说一嘴写时拷贝吧

我们可以对比一下写时拷贝修改内容前后内存的变化:

可以先看一下老师的图,并尝试分析一下问题1和2:

好,这里我来说明一下,

修改内容之前的内存状态

虚拟内存与物理内存页对应关系 :父进程和子进程各自的虚拟内存中的数据段和代码段,通过各自的页表项映射到相同的物理内存页上。例如,父进程页表项 50 和 100,以及子进程页表项 50 和 100,都指向同一块物理内存页,且都标记为 “只读”。

共享内存的优势 :这样做的好处是节省了内存资源。因为在子进程刚创建时,可能并不会对共享的内存页进行修改,如果一开始就复制一份副本,会浪费内存空间。

修改内容之后的内存状态

子进程尝试写入 :当子进程试图对共享的物理内存页进行写操作时,操作系统会检测到对该 “只读” 内存页的写入请求,从而触发一个异常或中断。

操作系统的处理 :操作系统会对此情况进行判断分类。如果是野指针等真正的错误,会终止进程;如果是合法的写操作,就会执行写时拷贝操作,即为子进程分配一块新的物理内存页,并将原来共享页面的内容复制到新的页面中,然后将子进程页表项对应页面的权限改为 “读写”,之后子进程就可以在这块新的物理内存页上进行写操作,而父进程仍然指向原来的物理内存页,保持其 “只读” 状态。

问题解答

问题 1 :如果在创建子进程之后直接将数据分开,即直接拷贝,会增加内存的占用。因为在很多情况下,子进程可能并不会对父进程的内存数据进行修改。而写时拷贝技术可以延迟拷贝操作,只有在子进程需要写入数据时才进行拷贝,这样可以有效节省内存资源,提高系统的性能和效率。

问题 2 :直接开辟对应的空间而不采用写时拷贝的话,会导致内存的浪费。因为每个子进程都需要为父进程的内存数据开辟一份完整的副本,即使这些数据在子进程中可能从未被修改过。而写时拷贝技术可以按需获取内存资源,只有在需要写入时才分配和拷贝必要的内存页面,实现了 “惰性” 申请内存,使内存的使用更加高效合理。如下述用例,当我们想对a++时,我们才会给他开辟新的内存空间,没有这条命令时,并不会给他开辟!

2.进程终止

2.1 进程终止的本质 

进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码。

2.2 进程退出场景

进程退出,无非就是三种情况:

1.代码跑完,结果对

2.代码跑完,结果不对

3.代码没跑完,进程异常了

以上3中情况中,情况12是由我们的退出码(稍后会做讲解)来决定的,即exit(code)

而情况三,我们要思考一个问题,那就是代码异常了,退出码本身,还有意义吗?

为了解决这个问题我们可以打个比方:
我们可以将上述过程类比为考试,代码跑没跑完,就好比你的考试过程顺不顺利,有没有中途产生意外,而结果就好比我们的考试分数,情况12就可以类比为我去考试了,也认真的考完了,只不过情况一拿了100分,而情况二我可能不及格,只拿了20分,好,相比大家应该都知道吧?我们考了一百分的时候很少有家长问原因,问为什么考这么高,但是,你要是考了20分,包先问原因的,而且搞不好还要请你吃一顿炒肉哈。那么,这就是我们退出码存在的意义了,倘若有一天,你和父母约法三章,说以后考试,我回来之后说0,就代表我考的不错,满分,但我要是考砸了,如果说1,代表我笔没油了导致考砸了,2代表考试时饿了导致考砸了,3代表考试时溜号想自己对象了导致考砸了等等等,这样一来,只要你考砸了,父母问你,你就回一个数字是不是就可以了?换句话说,这便是一种反馈,要让你父母知道你考砸的原因!(
现实中不建议这样做哈,容易再吃一份炒肉

这便是退出码存在的意义!!!

而我们再看情况三,就好比我在考试的时候作弊被抓了,取消考试资格了,那么,此时我考了多少分还重要吗?不重要,重要的是我是怎么被发现的,问题出现在哪里,以及回去怎么交代!同理我们回到计算机上,代码都错了都跑不了了,那我还回去关心你的退出码是几吗?我肯定当前最关注的是代码为什么会错!怎么去修改代码!所以上述问题也迎刃而解,即

代码异常了,退出码本身没有意义!

好,我们接下来再详细解释一下退出码相关的内容

2.3 退出码

大家还记得之前的C语言我们每次又要int main() return 0吗?那这里的0是什么?为啥不是return 1,return 2,return3呢?这里就要涉及退出码的概念了。

退出码的定义

退出码是一个整数值,通常用于指示程序的执行结果。一般情况下,退出码0表示程序成功执行,非零值则表示程序执行过程中遇到了某种问题或错误。

常见退出码例举

在这里给大家罗列了一些常用的退出码,如果有需要可以自己去百度上查一查

不愿意查的看这里,可以这样自己找

只要写一个这个文件:

将其编译运行就可以得到如下结果: 

查看退出码的方法 

在Linux中,可以通过特殊变量$?查看上一个命令的退出状态码。

echo $?

例如,我们可以写如下文件,来查看退出码!

文件内容如下所示:

我们来编译运行,查一下退出码:

 欸?我又敲了一遍echo $?,这回退出码咋变成0了?难道是我的文件改邪归正了吗?

实际上,并不是,我们的这个0是上一个进程echo $?的退出码,这个进程成功执行了,那当然退出码就是0了! 

2.4 exit函数

exit函数 是C语言中的一个标准库函数,用于正常终止一个程序。它定义在stdlib.h头文件中

void exit(int status);

 status:退出状态码,是一个整数值,用于向操作系统报告程序的执行状态。通常,0表示程序成功执行,非零值表示程序执行失败,不同的非零值可以表示不同的错误类型。

功能

执行清理操作:当调用exit函数时,它会先执行所有已注册的清理函数(通过atexit函数注册的函数)。这些清理函数通常用于释放资源、关闭文件等操作。

终止程序:在完成所有清理操作后,exit函数会终止程序的执行,并将status值作为退出状态码返回给操作系统。

代码测试:

我们写入如下内容:

编译运行: 

证明了返回值确实是exit里面的状态值。 

2.5 strerror

我们输入man 3 strerror查询一下向相关信息:

说明: 

参数

errnum:错误码,是一个整数值,通常由系统调用或库函数返回。

返回值

返回一个指向错误信息字符串的指针。该字符串描述了由errnum指定的错误。

3.前景回顾

3.1 进程退出的方式

前文我们提及了几点进程的退出方式,还有三种退出方法,分别为

方法1:在main函数中使用return n;,其中n是返回的退出码。

方法2:直接调用exit(n);,其中n是退出码。

方法3:直接调用_exit(n);,其中n是退出码。

那么,exit和_exit有什么区别呢?为什么要搞两个呢?

3.2 exit和_exit的区别

exit_exit 都是用来终止进程的函数,但它们在行为上有所不同。exit 函数会调用所有注册的清理函数(通过 atexit 注册),刷新所有标准I/O缓冲区,然后终止进程。这意味着它会确保所有的输出都被写入到文件或终端,并且执行任何必要的清理工作。相反,_exit 函数直接终止进程,不刷新缓冲区,也不调用任何清理函数,因此它通常用于需要立即终止进程且不需要进行任何清理的情况。简而言之,exit 提供了一个更优雅的退出方式,而 _exit 提供了一个快速且简单的退出方式。

换句话说,exit是终止进程,会主动刷新缓冲区,而_exit会直接终止进程,不会刷新缓冲区,那么话说回来,什么是进程终止?进程终止就是绝对要调用系统调用,必须让操作系统完整真正的的进程删除退出!

3.3 return 和 exit的区别

returnexit 在C语言中都用于结束程序,但return用于从函数返回一个值,可以是从main函数返回一个整数值给操作系统表示程序退出状态,而exit是一个库函数,用于立即终止整个程序,它接受一个整型参数作为退出状态码,并在终止前执行清理操作如刷新标准I/O缓冲区和调用已注册的atexit函数。简而言之,return通常用于函数内部,而exit用于程序的全局退出。

 4.进程等待

4.1 什么是进程等待?

进程等待是指在多进程环境中,一个进程(通常是父进程)暂停其执行,直到另一个进程(子进程)完成其任务并终止的行为,这个过程允许父进程同步子进程的结束,回收子进程占用的资源,并获取子进程的退出状态,以便于进行后续处理。

4.2 为什么要有进程等待?

防止僵尸进程

当子进程结束时,如果父进程没有及时回收子进程的资源,子进程可能会变成僵尸进程。僵尸进程会占用系统资源,导致内存泄漏。

获取子进程退出信息

父进程需要知道子进程是否成功完成任务,以及子进程的退出状态(退出码)。这有助于父进程根据子进程的执行结果进行相应的处理。

资源回收

父进程通过等待子进程,可以回收子进程占用的资源,如内存和文件描述符等。

4.3 如何实现进程等待?

4.3.1 使用 wait 函数

wait 函数用于父进程等待任意一个子进程结束。它返回结束的子进程的进程ID(PID)。如果子进程没有结束,wait 函数会阻塞父进程,直到一个子进程结束。如果子进程已经结束,wait 函数会立即返回,父进程可以获取子进程的退出信息。

4.3.2 wait函数原型

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);

说明:

其中pid_t wait(int *status);:该函数使父进程等待其某个子进程结束,并返回子进程的进程ID。参数status是一个指向整数的指针,用于存储子进程的终止状态;接收子进程的退出状态。成功时,wait 返回子进程的PID;出错时,返回-1。输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL。

在多进程编程中,父进程通常会fork多个子进程来执行并行任务。通过调用wait函数,父进程可以确保所有子进程都已完成,然后再进行进一步的处理。wait函数可以用于实现进程间的同步,确保父进程在子进程完成特定任务后再继续执行。而在多进程中,往往父进程最先创建,最后退出!

具体来说,父进程调用wait,表示父进程等待任意一个子进程:

1.如果子进程没有退出,父进程wait的时候,就会阻塞

2.如果子进程退出,父进程wait的时候,wait就会返回了,让系统自动解决子进程的僵尸问题,等待成功的时候,返回子进程的pid

4.3.3 waitpid函数

waitpid函数是wait函数的扩展,提供了更多的控制选项。它的原型如下:

pid_t waitpid(pid_t pid, int *status, int options);

说明:

pid:指定要等待的子进程的PID。可以是特定的PID、0(等待任意子进程)、负值(等待属于特定进程组的子进程)。

status:指向一个整数的指针,用于存储子进程的退出状态。如果不关心状态,可以设置为NULL。不仅仅是退出码,它包含了子进程的详细信息,包括退出码、终止信号、核心转储标志等。

options:控制等待行为的选项。常用的选项有WNOHANG(非阻塞等待)和WUNTRACED(也获取停止状态的子进程)。

 4.3.3.1 高16位和低16位

高16位用于存储核心转储标志和终止信号,低16位用于存储退出码。

核心转储标志和终止信号

核心转储标志:如果子进程因为信号而终止并且产生了核心转储,status的第7位(从0开始计数)会被设置为1。

终止信号:子进程因为信号而终止时,信号编号会被存储在status的高8位中。

退出码:如果子进程正常结束,退出码会被存储在status的低8位中。

常见的信号编号及其含义:

SIGKILL(9):立即终止进程,不能被捕获或忽略。

SIGTERM(15):请求终止进程,可以被捕获或忽略。

SIGINT(2):通常由Ctrl+C产生,请求终止进程。

SIGQUIT(3):请求终止并产生核心转储。

SIGSTOP(19):立即停止进程,不能被捕获或忽略。SIGCONT(18):继续执行被停止的进程。

进程退出场景

图中解释了如何判断进程是正常结束还是异常结束:

正常结束status的低8位不为0,表示进程的退出码。

异常结束status的高16位不为0,表示进程因为信号而终止。

附上老师画的图:

一般而言,进程的退出码范围是【0,255】

4.3.4 阻塞等待

 阻塞等待(Blocking Wait)是指在多进程或多线程编程中,一个进程或线程在等待某个条件或事件(如子进程结束、资源可用等)时,会暂停其执行,直到该条件或事件发生。这种行为称为阻塞等待,因为它会阻塞进程或线程的进一步执行,直到等待的条件得到满足。

本质就是检测子进程状态、退出,回收,没有退出立即返回

4.3.5 非阻塞等待

与阻塞等待相对的是非阻塞等待(Non-blocking Wait),在非阻塞等待中,进程或线程在等待条件满足时不会暂停执行,而是继续执行其他任务或定期检查等待条件是否满足。非阻塞等待通常需要更复杂的编程模型,如使用轮询、事件驱动或异步I/O等技术。

4.3.6 阻塞/非阻塞对比

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

 这里我们说一下options选项:

这个选项是控制等待行为的选项,可以是0或以下选项的组合:

WNOHANG:非阻塞等待。

WUNTRACED:也获取停止状态的子进程。

WNOHANG 选项的作用

非阻塞等待:当 options 参数包含 WNOHANG 时,如果指定的子进程尚未结束,waitpid() 函数会立即返回,而不是阻塞调用进程。

返回值:如果子进程已经结束,waitpid() 返回子进程的PID;如果子进程尚未结束(并且 WNOHANG 被设置),则返回0。

错误处理:如果发生错误,waitpid() 返回-1,并设置 errno 以指示错误原因。

额外多说一句,那个HANG在程序员口中很常见,如果一个程序员说什么东西hang住了,就是说什么东西宕机了!

这里讲一个故事,来理解一下,

假设张三是一个学渣,他有一个学霸朋友,叫李四,李四住在10楼,由于张三是学渣,所以不关注老师发布的消息,但是一天他突然听到室友说明天要Linux考试了,他慌的一批,于是给李四打电话,事情到这里便有了两条主线:

主线一:李四接到张三的电话后,得知他是想要借我的学霸笔记,但是李四自己还没有用完,于是便跟张三说,不行啊,我没用完,现在还不能借你,说完之后,张三说了一声好的,便挂掉了电话,之后过了两三分钟,张三电话又打来了,问:四啊,你的笔记用完了吗?四说还没呢!张三又回了一句好的,便又挂了电话,此种循环持续了n次,直到最后一次李四说好了,我用完了,于是张三如愿以偿接到了笔记!

主线二:李四接到张三的电话后,得知他是想要借我的学霸笔记,但是李四自己还没有用完,于是便跟张三说,不行啊,我没用完,现在还不能借你,说完之后,张三说了一声好的,但是没有挂掉电话,之后过了两三分钟,张三又问了,四啊,你的笔记用完了吗?四说还没呢!张三又回了一句好的,但是没有挂电话,通话一直保持着,此种模式持续了n次,直到最后一次李四说好了,我用完了,于是张三如愿以偿接到了笔记!

再举一个例子感受一下:

假设你在一个餐厅排队等待点餐。在你前面的人点餐时,你无法进行点餐操作,必须等待他们完成。在这个过程中,你不能做其他事情(比如看手机、聊天等),直到轮到你点餐。这就像进程在阻塞状态时的行为。

---------------------------------------------------------------------------------------------------------------------------------

假设你在一个自助餐厅。你可以在等待食物准备好的同时,先去拿饮料、餐具等。你不需要一直站在食物窗口前等待,而是可以边做其他事情边等待。这就像进程在非阻塞状态时的行为。

4.4 代码实战

while(1) {pid_t rid = waitpid(id, NULL, WNOHANG);if(rid == id) {printf("wait child success\n");break;} else if(rid == 0) {printf("child not quit!\n");sleep(1);} else if(rid < 0) {perror("wait error!\n");break;}
}

代码解释: 

  1. 非阻塞等待waitpid(id, NULL, WNOHANG) 调用 waitpid 函数,其中 id 是要等待的子进程的PID,NULL 表示不关心子进程的退出状态,WNOHANG 表示非阻塞等待。这意味着如果子进程尚未结束,waitpid 会立即返回而不是阻塞调用进程。

  2. 返回值检查

    • if(rid == id):如果 waitpid 返回的PID与要等待的子进程的PID相同,说明子进程已经结束,打印成功消息并跳出循环。

    • else if(rid == 0):如果 waitpid 返回0,说明子进程尚未结束,打印一条消息表示子进程还在运行,并休眠1秒后继续检查。这是非阻塞等待的典型行为。

    • else if(rid < 0):如果 waitpid 返回负值,说明发生了错误,打印错误消息并跳出循环。 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/81639.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring框架的设计目标,设计理念,和核心是什么 ?

Spring框架是一个为简化企业级应用开发而设计的开源框架&#xff0c;它提供了全面的基础设施支持&#xff0c;使得Java应用开发更加简单、快速和可维护。下面我将详细解释Spring框架的设计目标、设计理念以及核心组件。 设计目标 简化Java企业级应用开发&#xff1a;通过提供…

Red Hat6.4环境下搭建DNS服务器

DNS服务器&#xff08;Domain Name System Server&#xff09;是互联网中用于将域名&#xff08;如 www.example.com&#xff09;解析为IP地址&#xff08;如 192.0.2.1&#xff09;的服务器。它是互联网基础设施的重要组成部分&#xff0c;帮助用户通过易于记忆的域名访问网站…

Nginx核心功能 02

目录 Nginx代理技术核心概念 &#xff08;一&#xff09;正向代理&#xff08;Forward Proxy&#xff09; 1. 基本定义 2. 技术原理 3. 应用场景 &#xff08;二&#xff09;反向代理&#xff08;Reverse Proxy&#xff09; 1. 基本定义 2. 技术原理 3. 应用场景 一、…

关于Python:3. Python标准库和常用模块

1. os 和 sys&#xff08;系统编程基础&#xff09; 这两个模块是进行系统层面操作&#xff08;如文件管理、路径处理、环境变量访问等&#xff09;必不可少的工具。 os 模块 os 主要是用于与操作系统交互的&#xff0c;比如&#xff1a; 文件和目录操作 获取系统信息 运行…

Java基于SaaS模式多租户ERP系统源码

目录 一、系统概述 二、开发环境 三、系统功能介绍 一、系统概述 ERP&#xff0c;全称 Enterprise Resource Planning 即企业资源计划。是一种集成化的管理软件系统&#xff0c;它通过信息技术手段&#xff0c;将企业的各个业务流程和资源管理进行整合&#xff0c;以提高企业…

个人健康中枢的多元化AI网络革新与精准健康路径探析

引言 随着数字化转型的深入推进,个人健康中枢作为集成化健康管理系统,正在从传统的单一功能向多元化的AI驱动方向快速发展。在这一背景下,新兴网络硬件技术,特别是DPU(数据处理单元)和全光网络的出现,为个人健康中枢的革新提供了前所未有的机遇。本研究将深入探讨这些技…

AI跑得快,MCP来加速——模型计算平台在训练与推理中的硬核作用

AI跑得快,MCP来加速——模型计算平台在训练与推理中的硬核作用 一、引言:AI是“铁人三项”,但训练+推理常常“掉链子” 如今的人工智能系统越来越强,像ChatGPT、Stable Diffusion、Segment Anything等模型不断刷新技术天花板。但你是否也注意到: 明明模型设计得挺好,训练…

《MATLAB实战训练营:从入门到工业级应用》工程实用篇-自动驾驶初体验:车道线检测算法实战(MATLAB2016b版)

《MATLAB实战训练营&#xff1a;从入门到工业级应用》工程实用篇-&#x1f697; 自动驾驶初体验&#xff1a;车道线检测算法实战&#xff08;MATLAB2016b版&#xff09; 大家好&#xff01;今天我要带大家一起探索自动驾驶中一个非常基础但又至关重要的技术——车道线检测。我…

模型部署——cuda编程入门

CUDA中的线程与线程束 kernel是在device上线程中并行执行的函数&#xff0c;核函数用__global__符号声明&#xff0c;在调用时需要用<<<grid_size, block_size>>>来指定kernel要执行的线程数量。在CUDA中&#xff0c;每一个线程都要执行核函数&#xff0c;并…

WordPress不支持中文TAG标签出现404的解决方法

我们在后台编辑文章时输入中文标签会发现出现404的情况&#xff0c;其实中文TAG标签链接无法打开的原因是WordPress不支持中文的编码。那么解决的方法也很容易&#xff0c;只要改代码让WordPress能支持中文的编码形式&#xff0c;也就是UTF-8和GBK编码即可&#xff0c;无需用到…

金融信贷公司所需的技术和风控体系及其带来的价值

金融信贷公司的技术架构通过集成传统大型机系统与现代数据平台&#xff0c;能够有效支持金融信贷业务的运作&#xff0c;同时通过大数据、ETL、报表开发、数据仓库等技术为公司带来更高效的数据驱动决策、精准的风控分析和更灵活的业务支持。 一、公司技术架构 数据仓库架构&…

《AI大模型应知应会100篇》第43篇:大模型幻觉问题的识别与缓解方法

第43篇&#xff1a;大模型幻觉问题的识别与缓解方法 摘要 当AI系统自信满满地编造"量子计算机使用香蕉皮作为能源"这类荒谬结论时&#xff0c;我们不得不正视大模型的幻觉问题。本文通过15个真实案例解析、6种检测算法实现和3套工业级解决方案&#xff0c;带您掌握…

计算方法实验五 插值多项式的求法

【实验性质】 综合性验 【实验目的】 掌握Lagrange插值算法、Newton插值算法&#xff1b;理解Newton插值算法相对于Lagrange插值算法的优点。 【实验内容】 先用C语言自带的系统函数sin x求出 的值&#xff0c;然后分别用Lagrange、Newton方法求出的值&#xff0c;并与用…

文献总结:TPAMI端到端自动驾驶综述——End-to-End Autonomous Driving: Challenges and Frontiers

端到端自动驾驶综述 1. 文章基本信息2. 背景介绍3. 端到端自动驾驶主要使用方法3. 1 模仿学习3.2 强化学习 4. 测试基准4.1 真实世界评估4.2 在线/闭环仿真测试4.3 离线/开环测试评价 5. 端到端自动驾驶面临的挑战5.1 多模态输入5.2 对视觉表征的依赖5.3 基于模型的强化学习的世…

PostgreSQL:pgAdmin 4 使用教程

pgAdmin 4 是一个用于管理和维护 PostgreSQL 数据库的强大工具。它提供了一个图形化界面&#xff0c;使用户能够轻松地连接到数据库、创建表、运行 SQL 语句以及执行其他数据库管理任务。 安装和使用 安装 pgAdmin 4 安装 pgAdmin 4 非常简单。下载并运行安装程序&#xff0…

Java学习手册:关系型数据库基础

一、关系型数据库概述 关系型数据库是一种基于关系模型的数据库&#xff0c;它将数据组织成一个或多个表&#xff08;或称为关系&#xff09;&#xff0c;每个表由行和列组成。每一列都有一个唯一的名字&#xff0c;称为属性&#xff0c;表中的每一行是一个元组&#xff0c;代…

wpf CommandParameter 传递MouseWheelEventArgs参数

在 WPF 中通过 CommandParameter 传递 MouseWheelEventArgs 参数时&#xff0c;需结合 ‌事件到命令的转换机制‌ 和 ‌参数转换器‌ 来实现。以下是具体实现方案及注意事项&#xff1a; 一、核心实现方法 1. ‌使用 EventToCommand 传递原始事件参数‌ 通过 Interaction.Tr…

八大排序之选择排序

本篇文章将带你详细了解八大基本排序中的选择排序 目录 &#xff08;一&#xff09;选择排序的时间复杂度和空间复杂度及稳定性分析 &#xff08;二&#xff09;代码实现 (三)输出结果 选择排序的基本原理是&#xff1a;每次从待排序的数组中找出最大值和最小值。具体流程是…

【算法学习】哈希表篇:哈希表的使用场景和使用方法

算法学习&#xff1a; https://blog.csdn.net/2301_80220607/category_12922080.html?spm1001.2014.3001.5482 前言&#xff1a; 在之前学习数据结构时我们就学习了哈希表的使用方法&#xff0c;这里我们主要是针对哈希表的做题方法进行讲解&#xff0c;都是leetcode上的经典…

Java 中如何实现自定义类加载器,应用场景是什么?

在 Java 中&#xff0c;可以通过继承 java.lang.ClassLoader 类来实现自定义类加载器。自定义类加载器可以控制类的加载方式&#xff0c;实现一些特殊的应用场景。 实现自定义类加载器的步骤&#xff1a; 继承 java.lang.ClassLoader 类。 重写 findClass(String name) 方法 …