JVM之类的初始化与类加载机制

类的初始化

clinit

  • 初始化阶段就是执行类构造器方法clinit的过程。
  • 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
  • 构造器方法中指令按语句在源文件中出现的顺序执行。
  • clinit不同于类的构造器。(关联:构造器是虚拟机视角下的init
  • 若该类具有父类,JVM会保证子类的clinit执行前,父类的clinit已经执行完毕。
  • 虚拟机必须保证一个类的clinit方法在多线程下被同步加锁。

只有给类中的static的变量显示赋值或者静态代码块中赋值了,才会生成此方法.

哪些类不会生成clinit方法
  • 一个类中没有静态变量和静态代码块
  • 有静态变量,但是没有静态变量的显示赋值以及静态代码来执行初始化操作
  • 直接采用的 static final修饰的基本数据类型字段,这些字段在链接阶段的准备环节就已经初始化了
  • 不是显示常量赋值的话 而是调用方法去赋值还是会生成clinit方法的(在初始化阶段给他赋值)
clinit方法会死锁吗

虚拟机会保证一个类的clinit方法在多线程环境下被正确的加锁,同步(所以他多线程下是线程安全的),这同样会导致如果clinit方法中有耗时过场的操作,就可能导致线程阻塞而死锁,并且非常难以排查.

类加载的时机(什么情况会触发类的加载)

  • 当创建一个类的实例时,比如new关键字,或者是反射,克隆,反序列化等
  • 当调用类的静态方法时,即当使用了字节码invokestatic指令
  • 当使用类,接口的静态字段时(final修饰特殊考虑),比如使用getstatic或者putstatic指令
  • 当使用java.lang.reflect包中的方法反射类的方法时.比如Class.forname(“aa.bb.Test”)
  • 当初始化子类时,如果发现其父类还没有初始化,则需要先去触发父类的初始化
  • 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类初始化,该接口要在其之前被初始化
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类
  • 当初次调用MethodHandle实例时,初始化该MethodHandle指向方法所在的类

当Java虚拟机初始化一个类时,要求他所有的父类都已经被初始化,这条规则不适用于接口 \

  • 在初始化一个类时,并不会先初始化他所实现的接口
  • 在初始化一个接口时,并不会初始化他的父接口
    因此一个父接口并不会因为他的子接口或者实现类初始化而初始化,只有当程序首次使用特定接口的静态字段时,才会导致该接口初始化

被动使用不会触发类的初始化]

  • 通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类或接口的初始化,因为常量已经在链接准备阶段被显示赋值了
  • 调用ClassLoader类的loadClass()加载一个类,并不是对类的主动使用,不会导致类的初始化

被动的使用,意味着不需要执行初始化环节,意味着没有clinit的调用.

类的卸载

除非是你自定义的类加载器直接把这个类加载器也卸载掉(包括他下面的类),否则其他的已有的类加载器是不允许被卸载的,因为他们与类是双向绑定的关系.

类加载代码demo

public class T {

    public static int k = 0;
    public static T t1 = new T("t1");
    public static T t2 = new T("t2");
    public static int i = print("i");
    public static int n = 99;

    static {
        print("静态块");
    }

    public int j = print("j");

    {
        print("构造块");
    }

    public T(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + "   n=" + n);
        ++n;
        ++i;
    }

    public static int print(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + "   n=" + n);
        ++n;
        return ++i;
    }

    public static void main(String[] args) {

    }
}

运行结果

1:j i=0   n=0
2:构造块 i=1   n=1
3:t1 i=2   n=2
4:j i=3   n=3
5:构造块 i=4   n=4
6:t2 i=5   n=5
7:i i=6   n=6
8:静态块 i=7   n=99

类的加载器

类的加载分类

  • 显示加载:调用ClassLoader类中的方法去显示的加载某个类
  • 隐式加载:是由jvm自动加载到内存中的,用到哪些加载哪些

类的唯一性

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间,比较这两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义

命名空间
  • 每个类加载器都有自己的命名空间,由该加载器及其所有的父类加载器所加载的类组成
  • 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
  • 在不同的命名空间,有可能出现类的完整名字(包括包名)相同的情况

在大型应用中,可以借助这个特性,来运行一个类的不同版本

类加载的三个基本特征

  • 双亲委派模型: 也有不用这个机制的 ,比如上下文加载器
  • 可见性: 子类加载器可以访问父类加载器的类型,但是反过来不被允许.
  • 单一性: 由于父类加载器对于子加载器是可见的,所以父类加载过的类,在子类中不会重复加载.

类加载的分类

JVM支持两种类型的类加载器,分别为引导类加载器(BootStrap ClassLoader)和自定义类加载器(User-Defined ClassLoader).
从概念上讲,自定义加载器应该是程序中由开发人员自定义的加载器,但是Java的虚拟机规范并没有如此定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义加载器,而引导类加载器是由C++源码是实现的,在Java环境中也根本获取不到

启动类加载器(BootStrap ClassLoader )
  • 这个类加载使用C/C++语言实现,嵌套在JVM内部
  • 他用来加载Java的核心库,用于提供JVM自身所需要的类
  • 不继承自ClassLoader,没有父加载器
  • 出于安全考虑,只加载包名为java,javax,sun等开头的类
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
扩展类加载器(Extension ClassLoader)
  • Java语言编写,由ExtClassLoader实现
  • 继承自ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录下加载类库,如果用户创建的jar放在此目录下,也会由扩展类加载器加载
应用程序加载器(系统类加载器 AppClassLoader)
  • Java语言编写,由AppClassLoader实现
  • 继承自ClassLoader类
  • 父类加载器为启动类加载器
  • 他负责加载环境变量classPath或系统属性java.class.path指定路径下的类库
  • 应用程序中的类加载器默认是系统类加载器
  • 他是用户自定义类加载器的默认父加载器
  • 通过ClassLoader的getSystemClassLoader()可以获取到该类加载器
用户自定义类加载器

通过类加载器可以实现非常绝妙的插件机制.自定义的类加载器能够实现应用隔离,tomcat和spring都在内部实现了自定义类加载器,这样的机制比c/c++要优秀太多,自定义的类加载器通常都需要继承自抽象类ClassLoader

ClassLoader源码分析

什么是双亲委派机制

在加载器加载类的时候,如果加载器有父类(一般是组合定义为父类),先让父类去加载,一层层往上找,如果父类都没有再由自己加载,否则由某一个父类加载进来.

关键方法源码分析
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 {
	//在这里 Java源码判断了如果父类不为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
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    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;
        }
protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }
private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }
  • loadClass属于一个模板方法,不需要去重写他 否则有可能会破坏双亲委派机制
  • 子类可以去重写findClass方法,到这里是需要子类加载器自己去加载类方法了(有些像spring的感觉 新建和获取都是通过getSingletonBean去获取单例bean的)
  • defineClass是获取类的字节码流信息,并组装成class对象(这个方法也都会用到)
  • preDefineClass 中判断必须以java.开头,避免双亲委派机制遭到破坏时系统受到威胁(又加了一层判断确保安全性)
自定义类加载器
public class UserDefineClassLoader extends ClassLoader {

    private final String rootPath;

    public UserDefineClassLoader(String rootPath) {
        this.rootPath = rootPath;
    }

    @Override
    protected Class<?> findClass(String name) {
        //转换为以文件路径表示的文件
        String filePath = classToFilePath(name);
        //获取指定路径的class文件对应的二进制流数据
        byte[] data = getBytesFromPath(filePath);
        //自定义ClassLoader 内部需要调用defineClass() 把二进制流还原为Class实例
        return defineClass(name, data, 0, data.length);
    }

    private byte[] getBytesFromPath(String filePath) {
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        try {
            fis = new FileInputStream(filePath);
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private String classToFilePath(String name) {
        return rootPath + "\\" + name.replace(".", "\\") + ".class";
    }

    public static void main(String[] args) throws ClassNotFoundException {
        UserDefineClassLoader loader = new UserDefineClassLoader("D:\\code\\test");
        Class<?> aClass = loader.findClass("com.test.User");
        System.out.println(aClass);
		  System.out.println(aClass.getClassLoader());
    }

}

双亲委派类加载机制

优势与好处
  • 保护程序安全,防止核心的API类库被随意篡改
  • 避免类的重复加载,确保一个类的全局唯一性(当父类已经加载完成后,子类不会再去加载)
弊端
  • 下层的加载器可以访问上层父类的加载器都有什么类,但是上层是没有办法得知下层类加载器都有什么类,他是单向的
破坏双亲委派机制
  • 线程上下文加载器:(Thread.currentThread().getContextClassLoader()) jdbc等场景就是这么采用的
  • 热部署 (每一个程序模块都有一个自己的类加载器,当需要替换一个bundle时,就把bundle连同类加载器一起换掉以实现代码的热替换,这时候类加载处于一种更加复杂的网状结构)

tomcat的类加载机制

  • tomcat8可以配置<Loader delegate="true"/>表示遵循双亲委派机制
    类的结构图
    类的结构图
加载流程

当tomcat启动时,会创建几种类加载器
{fwtab}
{fwh}
{fwthead target=“1”} Bootstrap引导类加载器 {/fwthead}
{fwthead target=“2”} system系统类加载器 {/fwthead}
{fwthead target=“3”} tomcat自定义类加载器 {/fwthead}
{fwthead target=“4”} 加载顺序 {/fwthead}
{/fwh}
{fwb}
{fwtbody target=“1”}
加载jvm启动所需的类,以及标准扩展类(位于jre/lib/ext下)
{/fwtbody}
{fwtbody target=“2”}
加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定,位于CATALINA_HOME/bin下
{/fwtbody}
{fwtbody target=“3”}
Common/Catalina/Shared/WebappClassLoader
这些是tomcat自己定义的类加载器,它们分别加载’/common/,/server/,/shared/*'(在tomcat6以后已经合并到了根目录的lib目录下)和/WebApp/WEB-INF/*中的Java类库,其中WebApp类加载器和jsp类加载器通常会存在多个实例,每一个web应用程序对应一个WebApp类加载器,每一个jsp文件对应一个jsp类加载器

  • CommonClassLoader,Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个WebApp访问(加载CATALINA_HOME/lib下的结构,比如servlet-api.jar)
  • CatalinaClassLoader,Tomcat容器私有的类加载器,加载路径中的class对于WebApp不可见
  • SharedClassLoader,各个WebApp共享的类加载器,加载路径中的class对于所有WebApp可见,但是对于Tomcat容器不可见
    {/fwtbody}
    {fwtbody target=“4”}
    当应用需要某个类时,则会按照下面的顺序进行类加载
  • 使用bootstrap引导类加载器加载
  • 使用system系统类加载器加载
  • 使用应用类加载器在WEB-INF/classes中加载
  • 使用应用类加载器在WEB-INF/lib中加载
  • 使用common类加载器在CATALINA_HOME/lib中加载
    {/fwtbody}
    {/fwb}
    {/fwtab}

从图中的委派关系可以看出:
CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而
CatalinaClassLoader和SharedClassLoader自己能加载到类则与对方相互隔离.
WebAPPClassLoader可以使用SharedClassLoader加载到的类,但是每个WebAPPClassLoader实例之间互相隔离.
而JsperLoader的加载范围仅仅是这个jsp文件所编译出来的一个.class文件,他出现的目的就是为了被丢弃,当web容器检测到jsp文件被修改时,会替换掉目前的JsperLoader实例再去重建一个新的,以实现jsp文件的热修改功能

tomcat不遵循双亲委派机制会有风险吗

tomcat不遵循双亲委派机制,只是自定义的ClassLoader加载顺序不同,没有去严格的遵循双亲委派机制(我个人觉得这个设计是为了给每个WebApp更好的设置隔离性,以避免互相干扰),但是核心的jdk的api也是遵循双亲委派去由顶层的加载器加载的(他自己的核心api也是有自己的加载器去专门加载,也没有恶意篡改的风险)

tomcat作为web容器,要解决的问题是什么
  • 一个web容器可能需要部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,因此要保证每个应用程序的类库是独立的,保证相互的隔离性
  • 部署在用一个web容器中相同的类库相同的版本可以共享,否则就会出现多个相同的类加载进入虚拟机,显然这是要去尽力避免的
  • web容器自身也需要有一个自己的类库支持,他不能与应用的类库所混淆,基于安全上的考虑,应该让容器的类库和程序的类库相互隔离开
  • 支持jsp的热修改(那时候的jsp属于主流技术,还是非常流行的 现在都是动静分离,前后分离了)
如果Tomcat的CommonClassLoader想加载WebAppClassLoader中的类怎么办

用线程上下文加载器

为什么Java文件放在eclipse/idea中的src文件夹下会优先于jar包中的class

tomcat破坏双亲委派机制,自己指定的加载顺序 先使用应用类加载器在WEB-INF/classes中加载,再使用应用类加载器在WEB-INF/lib中加载

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

连锁药店系统:如何提高效率和客户满意度?

连锁药店系统是一种用于提高效率和客户满意度的软件系统&#xff0c;它能够管理多个药店的日常营运。通过这种系统&#xff0c;药店可以更好地管理库存、员工、销售和客户信息等方面&#xff0c;从而提高整体的经营效率。 首先&#xff0c;连锁药店系统能够帮助药店管理库存。系…

算法刷题总结 (十一) 二叉树

算法总结11 二叉树 一、二叉树的概念1.1、什么是二叉树&#xff1f;1.2、二叉树的常见类型1.2.1、无数值&#xff08;1&#xff09;、满二叉树&#xff08;2&#xff09;、完全二叉树 1.2.2、有数值&#xff08;3&#xff09;、二叉搜索树&#xff08;4&#xff09;、平衡二叉搜…

数字孪生与物流园区:优化布局规划的关键

随着全球贸易的增长和物流行业的发展&#xff0c;物流园区作为重要的物流枢纽和供应链管理中心&#xff0c;扮演着至关重要的角色。而数字孪生技术的出现为物流园区的运营和管理带来了革命性的变化。数字孪生技术是一种将实体物体与其数字化模型相结合的创新技术&#xff0c;通…

【UEFI】BIOS 阶段全局变量类型

BIOS的几个阶段需要不同阶段的数据传递&#xff0c;下面介绍4个全局变量。 1 固件存储介绍 本规范描述了应该如何在非易失性存储器中存储和访问文件。固件实现必须支持标准的PI固件卷和固件文件系统格式&#xff08;下文所述&#xff09;&#xff0c;但可能支持其他存储格式。…

什么是一致性哈希?一致性哈希是如何工作的?如何设计一致性哈希?

1.什么是一致性哈希&#xff1f;一致性哈希是如何工作的&#xff1f;如何设计一致性哈希&#xff1f;05-25 2.系统设计&#xff1a;从零用户扩展到百万用户05-28 收起 如果你有 n 个缓存服务器&#xff0c;一个常见的负载均衡方式是使用以下的哈希方法&#xff1a; 服务器索…

强连通分量-tarjan算法缩点

一. 什么是强连通分量&#xff1f; 强连通分量&#xff1a;在有向图G中&#xff0c;如果两个顶点u,v间&#xff08;u->v&#xff09;有一条从u到v的有向路径&#xff0c;同时还有一条从v到u的有向路径&#xff0c;则称两个顶点强连通(strongly connected)。如果有向图G的每…

NLP实战:调用Gensim库训练Word2Vec模型

目录 一、准备工作 1. 安装Gensim库 2. 对原始语料分词 二、训练Word2Vec模型 三、模型应用 1.计算词汇相似度 ​编辑 2. 找出不匹配的词汇 3. 计算词汇的词频 四、总结 &#x1f368; 本文为[&#x1f517;365天深度学习训练营]内部限免文章&#xff08;版权归 *K同学…

Flask-RESTful的使用

Flask-RESTful的使用 Flask-RESTful基本使用安装定义资源Resources创建API实例添加资源到API运行Flask应用 请求处理请求解析参数校验 响应处理数据序列化定制返回格式 其他功能蓝图装饰器集合路由命名规范路由名称 Flask-RESTful Flask-RESTful是一个用于构建RESTful API的扩展…

C++类和对象 -- 知识点补充

补充 const成员函数static成员友元内部类匿名对象拷贝对象时的一些编译器优化 const成员函数 将const修饰的成员函数称为const成员函数&#xff0c;const修饰类成员函数&#xff0c;实际是修饰该成员函数隐含的this指针&#xff0c;表明在该成员函数中不能对类的成员进行修改。…

使用MockJS进行前端开发中的数据模拟

在前端开发中&#xff0c;有时我们需要在没有后端接口的情况下进行前端页面的开发和测试。这时&#xff0c;我们可以使用MockJS来模拟数据&#xff0c;以便进行开发和调试。MockJS是一个用于生成随机数据和拦截Ajax请求的JavaScript库&#xff0c;它能够帮助我们快速搭建起一个…

Linux---用户切换命令(su命令、sudo命令、exit命令)

1. su命令 root用户拥有最大的系统操作权限&#xff0c;而普通用户在许多地方的权限是受限的。 普通用户的权限&#xff0c;一般在其HOME目录内是不受限的。 一旦出了HOME目录&#xff0c;大多数地方&#xff0c;普通用户仅有只读和执行权限&#xff0c;无修改权限。 su 是…

【操作系统】01.操作系统概论

操作系统的发展历史 未配置操作系统 手工操作阶段 用户独占全机&#xff0c;人机速度矛盾导致系统资源利用率低 脱机输入输出方式 为了缓解主机cpu和IO设备之间速度不匹配的矛盾&#xff0c;出现了脱机IO技术 在外围机的控制下&#xff0c;通过输入设备&#xff0c;将数据输…

耗时1周整理了网络安全学习路线,非常详细!

前言 这一期就出一个怎么学习网络安全的学习路线和方法&#xff0c;觉得有用的话三连收藏下 首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#xff0c;有些人会倒在学习linux系统及命令的路上…

数论专题(3)逆元

目录 初步认识 逆元 定义 应用 费马小定理 好久没有更新我们的数论专题板块了&#xff0c;今天&#xff0c;我们就来探究一下新知——逆元。 初步认识 在数据非常大的情景下&#xff0c;我们通常会对数据先进行取模运算&#xff0c;来计算在一定的范围内进行处理。而运算…

Java 进阶 -- 集合(一)

本节描述Java集合框架。在这里&#xff0c;您将了解什么是集合&#xff0c;以及它们如何使您的工作更轻松&#xff0c;程序更好。您将了解组成Java Collections Framework的核心元素——接口、实现、聚合操作和算法。 介绍告诉您集合是什么&#xff0c;以及它们如何使您的工作…

day4,day5 -java集合框架

List、Set、Map等常用集合类的特点和用法。 常用集合类&#xff08;List、Set、Map 等&#xff09;是 Java 中提供的数据结构&#xff0c;用于存储和操作一组数据。以下是它们的特点和用法&#xff1a; List&#xff08;列表&#xff09;: 特点&#xff1a;有序集合&#xff0…

《深入理解计算机系统(CSAPP)》第8章 异常控制流 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习CSAPP时的个人笔记&#xff0c;分享出来与大家学习交流&#xff0c;目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记&#xff0c;在复习回看时发现部分内容存在一些小问题&#xff0c;因时间紧张来不及再次整理…

Android 12系统源码_WindowInsets (一)WindowInsets相关类和功能介绍

一、什么是WindowInsets? WindowInsets源码解释为Window Content的一系列插值集合,可以理解为可以将其理解为不同的窗口装饰区域类型,比如一个Activity相对于手机屏幕需要空出的地方以腾给StatusBar、Ime、NavigationBar等系统窗口,具体表现为该区域需要的上下左右的宽高。…

如何强制删除文件夹?这样操作就能搞定!

案例&#xff1a;我想删掉一些没有用的文件夹&#xff0c;释放一些电脑内存&#xff0c;但是我发现&#xff0c;有些文件夹并不能直接被删除。怎样才能删除这些文件夹&#xff1f;有没有小伙伴有解决的办法。 在使用电脑过程中&#xff0c;我们可能会遇到一些无法正常删除文件夹…

操作系统-进程和线程-进程和线程

目录 一、进程的概念、组成、特征 二、进程的状态与转换、组织 2.1进程状态 2.2进程转换关系 2.3进程的组织 链接方式 索引方式 三、进程控制 3.1进程的创建 3.2进程的终止 3.3进程的阻塞和唤醒 3.4进程的切换 ​编辑 四、进程通信 4.1共享存储 4.2消息传递 直接通信…