【JVM】class文件格式,JVM加载class文件流程,JVM运行时内存区域,对象分配内存流程

这篇文章本来只是想讲一下class文件格式,讲着讲着越讲越多。JVM这一块吧,知识比较散比较多,如果深研究下去如死扣《深入理解Java虚拟机》,这本书很深很细,全记住是不可能的,其实也没必要。趁这个机会直接把标题中的这些的主要知识点都总结一下,不会过深,也不会是太浮于表面的八股文,总结一下比较好记,也省的后面自己再忘了。

主要内容包括以下几部分:
class文件格式
JVM加载class文件流程
JVM运行时内存区域
对象分配内存流程

一,class文件格式

在idea中,一般我们直接点开target目录中的class文件,idea会帮助我们反编译后展示为java文件,那么如何观察到class文件的格式呢?

这里我们需要在idea中安装一个Jclasslib的插件,如下图所示:
在这里插入图片描述
鼠标光标停在类名上,点击view->show bytecode with jclasslib,即可查看到class文件。如下图所示:

在这里插入图片描述
class文件[注2]包含但不限于以下几个部分,

开头是著名的魔数0xCAFEBABE(如果没有特殊说明,这里及下面的进制都是16进制),CAFEBABE这个魔数是用于标识和校验这个文件是否是一个class文件的。

此外,class文件中还包括如Minor VersionMajor Version,用于标识Java版本号;

constant_pool(常量池),常量池用于存储字面量(Literal)符号引用(Symbolic References)。字面量比较接近Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,主要包含下面几类常量[1]:
被模块导出或者开放的包(Package)
类和接口的全限定名(Fully Qualified Name)
字段的名称和描述符(Descriptor)
方法的名称和描述符()
方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
常量池中的这些常量和符号引用,在JVM运行时会存放到方法区中的常量池中[1]。而如Fields、Methods等元数据信息,存放在方法区中。

另外,什么是符号引用呢?这就涉及到了JVM的懒加载机制[2],就是说在编译成字节码class文件后,JVM不会立即加载class文件,而是用到的时候再进行加载[注1]。这就导致了class文件在被编译出来后,无法立即被分配到内存,也就没有直接指向内存的指针。那么,我们的字节码指令,又是如何调用类、方法、成员变量的呢?

在字节码class文件中,方法、成员变量、类以全限定名(即字符串常量)的方式被引用。如下图所示,Fieldref_info,即存储指向成员变量的指针(在下图中举例的是指向字符串String s),可以看到,描述了其类的全限定名、成员变量的名字和其类型的全限定名,点击跳转到字段,会跳到字段中,即完成了通过字符串常量作为指针,跳转到字段/方法/类的功能,这就是所谓的字符引用,说白了就是把全限定名的字符串作为引用的标识。

这些符号引用一部分会在类加载阶段或者第一次使用的时候就要被转化为直接引用,这种转换成为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接[1]。

java虚拟机支持5种方法调用字节码指令:
invokestatic:调用静态方法
invokespecial:用于调用实例构造器()方法、私有方法和父类中的方法
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时再确定一个实现该接口的对象
invokedynamic:先在运行时动态解析出调用带你限定符所引用的方法,然后再执行该方法。前面4条调用指令,分派逻辑都固化在Java虚拟机内部,而invokedynamic指令的分派逻辑是由用户设定的引导方法来决定的。

其中只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,Java语言里符合这个条件的方法共有静态方法、私有方法、实例构造器、父类方法4种,再加上被final修饰的方法(尽管它使用invokevirtual指令调用),这5种方法调用会在类加载的时候就可以把符号引用解析为该方法的直接引用。这些方法统称为“非虚方法”(Non-Virtual-Method),与之相反,其他方法就被称为“虚方法”[1]。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

讲完了常量池,class文件中剩下的就比较好讲了。如描述字段的表fields,描述方法的表methods,描述接口的表等等。这些就不展开讲了,如果对其他部分感兴趣,可以自己去查资料或者自己下一个jclasslib去看一看。

二,JVM加载class文件流程

在javac编译完class文件后,显而易见,如果要使用这些类,需要去加载它们(这里说的加载,指的是广义上的加载,不是loading这一步)。JVM加载class文件,分为以下几步,

在这里插入图片描述

第一步,加载(loading),《深入理解Java虚拟机》中是这么说的:*通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口。*听起来是有点复杂,其实就是把双亲委派机制的工作内容说出来了:通过类的全限定名,获取其字节流,在堆中申请class对象的内存空间,把类的各种元数据如方法表、属性表、常量池等放入方法区。双亲委派机制就是用来加载类的,具体的可以看我上一篇文章,讲的就是双亲委派机制是如何工作的[4]。

第二步,连接(linking),这一步中分3小步。其中第一小步,验证(verification),验证文件格式(如魔数是否为CAFEBABE、主次版本号等)、验证元数据、验证字节码、验证符号引用。总之就是要验证文件格式是否符合JVM规范,看看是否是一个合格的class文件格式;第二小步,准备(preparation),给静态变量赋默认值;第三小步,解析(resolution),将常量池内的符号引用替换为直接引用,即前面提到的静态解析。

第三步,初始化(initializing),这一阶段会调用类构造器< clinit>()。

需要注意的是,< clinit>()构造方法,并不是实例对象的构造方法< init>()。《深入理解Java虚拟机》中讲:< clinit>()构造方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{})中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的。 我个人的理解就是,在loading阶段申请了这个类的class对象的内存,在initializing阶段,会通过编译器自动生成的类构造器方法来完成类class对象的初始化,即给静态变量和静态语句块中的变量赋初始值。毕竟静态变量是类的成员变量。

三,JVM运行时内存区域

前文讲了编译出的class文件结构,JVM加载class文件流程,剩下的我们聊一下JVM在运行时的内存区域的划分。

JVM内存可以划分为程序计数器(Program Counter, PC)Java虚拟机栈(Java Virtual Machine stack, JVM stack)本地方法栈(native method stacks)方法区(method area)堆(Heap)直接内存(Derict Memory)

其中程序计数器和Java虚拟机栈是线程私有的,本地方法栈、方法区、堆、直接内存都是线程公有的。

程序计数器,这一块内存区域较小,可以看作是当前线程所执行的字节码的行号指示器。

Java虚拟机栈,虚拟机栈描述的是Java方法执行的线程内存模型,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

对于执行引擎来说,在活动线程中,只有位于栈顶的方法才是运行的,只有位于栈顶的栈帧才是生效的,其被称为”当前栈帧“(Current Stack Frame),与这个栈帧所关联的方法被称为”当前方法“(Current Method)。执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。

每一个栈帧都包括了局部变量表、操作数栈、动态连接、方法返回地址和一些额外的附加信息。

其中,局部变量表(Local Variables Table)是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。

操作数栈(Operand Stack)也常被称为操作栈,它是一个后入先出(Last In First Out, LIFO)栈。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。实际上因为有的方法进行静态解析而不是动态连接(还记得吗,能被invokestatic和invokespecial指令调用的方法和被final修饰的方法都会进行静态解析),不是所有栈帧的这个方法引用都能进行动态连接。

这么描述可能有点抽象,我们结合jclasslib来看一下

很简单的一段代码

在这里插入图片描述

对应的字节码

在这里插入图片描述

下面的LocalVariable Table为局部变量表

在这里插入图片描述

看到字节码望而生畏,这么多字节码指令记不住怎么办?字节码不需要记,需要的话查就好了(一般也用不到debug到字节码的程度吧,除了一些面试用的题…),左键单击字节码指令,再点击Show JVM Spec,即可跳转到Java虚拟机规范中对应字节码的查询结果[5]。

在这里插入图片描述

在这里插入图片描述

m方法字节码流程如下:
iconst_0,《JVM虚拟机规范》[5]中规定如下,因此iconst_0意为将整数int 0压入操作数栈

在这里插入图片描述

istore_1,注意description中的描述,istore_< n>中的n,是当前栈帧的局部变量表的索引值,istore_1是将操作数栈顶的整数类型变量,即栈顶的数0弹出操作数栈,并且赋值给局部变量表索引为1的位置,即给局部变量i赋值为0。iconst_0和istore_1结合起来,就是代码int i = 0;

在这里插入图片描述

在这里插入图片描述

iload_1,将局部变量表中索引为1的位置的数据弹出,并压入操作数栈中。

在这里插入图片描述

iinc 1 by 1,将局部变量表中索引为1位置的数+1 。注意这个操作和操作数栈没有关系。完成了i++

在这里插入图片描述

istore_2,将操作数栈中的数0弹出,赋值给局部变量表中2号索引位置。即int j = 0;

最后,返回,没有任何返回值。如果当前栈帧(m方法)下面还有其他栈帧,当前栈帧return后弹出,会由调用m方法的栈帧继续执行,成为新的当前栈帧。当然,这里m方法没有任何调用,到此,字节码结束。

有一些面试题会考i = i++和i = ++i的区别,实质上就是字节码istore_1和iinc 1 by 1顺序调换导致的。当然任何人都不会在项目中这么写代码,考这个纯粹为了考而考了…

在这里插入图片描述

Java虚拟机栈说到这里。

本地方法栈(Native Method Stacks),与虚拟机栈所发挥的作用是非常相似的,其区别是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。《Java虚拟机规范》对本地方法栈中的方法使用的语言、使用方式和数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它[1]。

Java堆(Java Heap),对于Java应用来说,Java堆是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作”GC堆“(Garbage Collected Heap)[1]。

从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现”新生代“ ”老年代“ “永久代” “Eden空间” “From Survivor空间” "To Survivor空间"等名词,在这里笔者想先说明的是这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局,更不是《Java虚拟机规范》里对Java堆的进一步细致划分[1]。

需要注意的是,尽管Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB),以提升对象分配时的效率。TLAB占用伊甸区eden,默认1%。用于给小对象在多线程的情况下不用竞争eden区分配就可以申请空间,提高效率。

方法区(Method Area),线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

其中,**运行时常量池(Runtime Constant Pool)**是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

说到方法区,不得不提一下“永久代”这个概念,尤其是在JDK8之前,许多程序员习惯在HotSpot虚拟机上开发、部署程序,很多人都更愿意把方法区称呼为“永久代”(Permanent Generation),或将两者混为一谈。本质上这两者不是等价的,因为仅仅是当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用分代来实现方法区而已,这样使得HotSpot的垃圾收集器能够像管理Java堆一样管理这些内存,省去专门为方法区编写内存管理代码的工作。但是对于其他虚拟机实现,譬如BEAJRockit、IBM J9等来说,是不存在永久代概念的。原则上如何实现方法区属于虚拟机的实现细节,不受《Java虚拟机规范》管束,并不要求统一。但是回过头来看,当年使用永久代来实现方法区的决定并不是一个好主意,这种设计使得Java应用更容易遇到内存溢出的问题。到了JDK7的HotSpot,已经把原来存放在永久代的字符串常量池、静态变量等移出,到了JDK8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta Space)来代替,把JDK7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。

直接内存(Derict Memory),直接内存并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁的使用,而且也可能导致OutOfMemoryError异常出现,所以我们放到这里一起讲解。

在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

显然,本机直接内存的分配不会收到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域综合大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

四,对象分配内存的流程

我们通常堆(Heap)是用于存储对象实例的,那么一个对象是直接就被分配到堆中吗?并不是的。但是在了解对象的分配流程之前,我们需要先了解一些内容。

栈上分配,满足栈上分配的要求如下:
线程私有小对象
无逃逸
支持标量替换

需要说明的是,栈上分配需要预先开启标量替换和逃逸分析才能使用[6],但是一般情况下,无需对栈上分配这两项设置进行更改和调优。

其中,逃逸指的是这个对象被多个线程共享,无逃逸即这个对象就在这一段代码中使用,没有return出去[6];而标量替换是说把一个对象中的字段直接分配到栈上,用于代替整个对象。

分配缓冲区(Thread Local Allocation Buffer, TLAB)(前文讲过,这里再复述一遍),Java堆中可以划分出多个线程私有的分配缓冲区,以提升对象分配时的效率。TLAB占用伊甸区eden,默认1%。用于给小对象在多线程的情况下不用竞争eden区分配就可以申请空间,提高效率。满足TLAB的条件如下:
小对象

一个对象分配内存的流程如下
如果能够栈上分配,就栈上分配;

剩下的对象,看看是否足够大,如果足够大,直接分配到老年代。

剩下的对象,看看是否满足分配到TLAB,如果可以,分配到Eden区中的TLAB。

剩下的对象,分配到Eden区。

Eden区(包括TLAB)中的对象,等到年轻代空间满了,会触发YGC对年轻代进行GC。经历了YGC后,还存活的对象,会迁移到S1区。S1区的对象经历YGC,会进入S2区;S2区的对象经历YGC,会进入S1区。当S1区和S2区的对象在经历了YGC后达到了进入老年代的要求后,就可以进入老年代。

老年代一旦触发FGC,就会开始对整个堆进行GC,存活的对象仍然留在老年代中,直到被GC回收。

在这里插入图片描述

这里解释一下关于JVM分代的一些术语,如前文所述,部分JVM实现是按照分代设计的,按GC后存活次数分为老年代,年轻代,年轻代分为Eden区(伊甸区),S1区(Survivor1区),S2区(Survivor2区)(也有的叫做S0区和S1区,都是一个意思)。笼统的讲,新生的对象会进入Eden区,经历了YGC会进入S1区,随后在S1和S2区中间来回倒腾,直到进入老年代为止。

YGC,即Young GC或者Minor GC,是年轻代触发GC后对年轻代的垃圾回收,包括Eden和S1 S2。

FGC,即Full GC或者Major GC,是老年代触发GC后对整个堆的垃圾回收。

前文说,”对象在S1和S2中间来回移动直到满足进入老年代的条件为止“。进入老年代的条件有二,满足其一即可进入老年代:

1,超过-XX:MaxTenuringThreshold指定次数(YGC)
对于常见的垃圾回收器,这个值是不同的,如

Parallel Scavenge   15
CMS    6
G1   15

2,满足动态年龄要求,即
在Survivor空间中低于或等于某年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到-XX:MaxTenuringThreshold中要求的年龄。

注1[3]:
严格来讲,JVM没有规定何时加载,但是规定了什么时候必须初始化。
new getstatic putstatic invokestatic指令,访问final变量除外
java.lang.reflect对类进行反射调用
初始化子类的时候,父类首先初始化
虚拟机启动时,被执行的主类必须初始化
动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic 的方法句柄时,该类必须初始化

注2:
一般来说,我们说Java文件编译成为class文件字节码,再由JVM的解释器(Bytecode Interpreter)逐行解释为机器码,这是Java或者说JVM能够跨平台的关键。其实Java中有一种即时编译机制JIT(Just In-Time compiler),这种机制可以将代码编译为机器码,进而不需要每次执行热点代码都需要解释执行——编译为机器码后直接执行机器码即可。
Java默认为混合模式,即混合使用解释器+热点代码编译,起始阶段采用解释执行,通过热点代码探测,检测到热点代码后,对其进行JIT即时编译,并对其进行编译执行。
-Xmixed 默认混合模式,开始时为解释执行,启动速度较快,对热点代码进行解释和编译
-Xint 使用解释模式,启动很快,执行稍慢
-Xcomp 使用纯编译模式,执行很快,启动很慢
我们通过java -version,能够看到最后显示mixed mode,即此Java为混合模式。可以通过上述VM参数改变为解释模式或者纯编译模式。

在这里插入图片描述

参考文章:
[1],深入理解Java虚拟机,周志明,第三版
[2],谈谈对Java中符号引用和引用的理解
[3],03_class_loading_linking_initializing.pdf
[4],【JVM】简述类加载器及双亲委派机制
[5],Chapter 6. The Java Virtual Machine Instruction Set
[6],jvm:优化-栈上分配

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

[Java EE] 多线程(六):线程池与定时器

1. 线程池 1.1 什么是线程池 我们前面提到,线程的创建要比进程开销小,但是如果线程的创建/销毁比较频繁,开销也会比较大.所以我们便引入了线程池,线程池的作用就是提前把线程都创建好,放到用户态代码中写的数据结构中,后面就可以随用随取. 线程池最大的好处就是减少每次启动,…

【Canvas】给图片绘制矩形以及添加文字

效果图: <!DOCTYPE html> <html lang"en"><head><title>Canvas Marker Example</title></head><body><!-- 图片 --><imgid"myImage"src图片地址alt"Image to mark"style"display: no…

java-函数式编程-函数对象

定义 什么是合格的函数&#xff1f;无论多少次执行函数&#xff0c;只要输入一样&#xff0c;输出就不会改变 对象方法的简写 其实在类中&#xff0c;我们很多参数中都有一个this&#xff0c;被隐藏传入了 函数也可以作为对象传递&#xff0c;lambda就是很好的例子 函数式接口中…

uniapp0基础编写安卓原生插件和调用第三方jar包和编写语音播报插件之使用jar包插件

前言 如果你不会编写安卓插件,你可以先看看我之前零基础的文章(uniapp0基础编写安卓原生插件和调用第三方jar包和编写语音播报插件之零基础编写安卓插件), 我们使用第三方包,jar包编写安卓插件 开始 把依赖包,放到某个模块的/libs目录(myTestPlug/libs) 还要到build…

Source-Free Domain Adaptation for Semantic Segmentation

Batch Normalization Statistics (BNS)&#xff0c;dual attention module (DAM).dual attention distillation (DAD)&#xff0c;intra-domain patch-level self-supervision module (IPSM).ADV means adversarial 引用的文献较老&#xff0c;不建议复现

java面试(MySQL)

优化 如何定位慢查询 方案一&#xff1a;开源工具 调试工具&#xff1a;Arthas 运维工具&#xff1a;Prometheus,Skywalking 方案二&#xff1a;MySQL自带慢日志 慢查询日志记录了所有执行时间超过指定参数&#xff08;llong_query_time,单位&#xff1a;秒&#xff0c;默认十…

操作系统(2)——进程线程

目录 小程一言专栏链接: [link](http://t.csdnimg.cn/8MJA9)基础概念线程详解进程详解进程间通信调度常用调度算法 重要问题哲学家进餐问题问题的描述策略 读者-写者问题问题的描述两种情况策略 总结进程线程一句话 小程一言 本操作系统专栏&#xff0c;是小程在学操作系统的过…

专注 APT 攻击与防御—工具介绍Veil-Evasion

专注 APT 攻击与防御 - Micro8 系列教程项目地址&#xff1a;https://github.com/Veil-Framework/Veil-Evasion 1、Veil-Evasion Veil-Evasion 是与 Metasploit 生成相兼容的 Payload 的一款辅助框架&#xff0c;并可以绕过大多数的杀软。 Veil-Evasion 并没有集成在kali&am…

macOS sonoma 14.4.1编译JDK 12

macOS sonoma 14.4.1编译JDK 12 环境参考文档开始简述问题心路历程着手解决最终解决(前面有点啰嗦了&#xff0c;可以直接看这里) 记录一次靠自己看代码解决问题的经历(总之就是非常开心)。 首先&#xff0c;先diss一下bing&#xff0c;我差一点就放弃了。 环境 macOS sonom…

nginx--自定义日志跳转长连接文件缓存状态页

自定义日志服务 [rootlocalhost ~]# cat /apps/nginx/conf/conf.d/pc.conf server {listen 80;server_name www.fxq.com;error_log /data/nginx/logs/fxq-error.log info;access_log /data/nginx/logs/fxq-access.log main;location / {root /data/nginx/html/pc;index index…

Copilot Venture Studio創始合伙人楊林苑確認出席“邊緣智能2024 - AI開發者峰會”

隨著AI技術的迅猛發展&#xff0c;全球正逐步進入邊緣計算智能化與分布式AI深度融合的新時代&#xff0c;共同書寫著分布式智能創新應用的壯麗篇章。邊緣智能&#xff0c;作為融合邊緣計算和智能技術的新興領域&#xff0c;正逐漸成為推動AI發展的關鍵力量。借助分布式和去中心…

在Mac上恢复已删除文件夹的最佳方法

“嗨&#xff0c;我从我的Mac Documents文件夹中删除了很多文件夹。已删除的文件夹包含我的重要文档和文件&#xff0c;是否可以取回它们&#xff1f;垃圾桶已被清洁软件清空。如何在我的Mac上恢复已删除的文件夹&#xff1f; 当您在 Mac 上删除 1 或 2 个文件夹时&#xff0c…

字符串函数与字符函数运用(1)

字符串与字符函数介绍1 前言一、字符分类函数字符函数练习 二、字符函数转换1.引入库2.代码改进 字符串函数strlen函数strcpy 结尾 前言 字符串函数大概有以下这几种 strcpy、strcat 、strcmp、strncpy、strncat、strncmp、strstr、strtok、strerror 这些函数可以很好的解决你…

Java 笔记 12:Java 方法的相关使用,方法重载、参数传递,以及递归等内容

一、前言 记录时间 [2024-05-02] 系列文章简摘&#xff1a; Java 笔记 01&#xff1a;Java 概述&#xff0c;MarkDown 常用语法整理 Java 笔记 02&#xff1a;Java 开发环境的搭建&#xff0c;IDEA / Notepad / JDK 安装及环境配置&#xff0c;编写第一个 Java 程序 Java 笔记 …

队列以及信号量

什么是队列 队列又称消息队列&#xff0c;是一种常用于任务间通信的数据结构&#xff0c;队列可以在任务与任务间、中断和任 务间传递信息。 为什么不使用全局变量&#xff1f; 如果使用全局变量&#xff0c;兔子&#xff08;任务1&#xff09;修改了变量 a &#xff0c;等待树…

vulnhub靶场之FunBox-1

一.环境搭建 1.靶场描述 Boot2Root ! This is a reallife szenario, but easy going. You have to enumerate and understand the szenario to get the root-flag in round about 20min. This VM is created/tested with Virtualbox. Maybe it works with vmware. If you n…

81、动态规划-爬楼梯

思路: 爬楼梯是一个特别经典的动态规划题&#xff0c;动态规划最好的办法就是从递归改到动态规划。 比如现在n阶楼梯&#xff0c;每次爬1阶或者2阶&#xff0c;一共有多少种方法。那么我就可以全排列&#xff0c;比如当前我可以走一阶算一下有多少种方法&#xff0c;然后我可…

1.C#图像区域分割与提取

&#xff08;1&#xff09;创建一个名为SplitImage的窗体的应用程序&#xff0c;将窗体改名为FormSplitImage。 &#xff08;2&#xff09;创建一个名为ImageProcessingLibrary的类库程序&#xff0c;为该工程添加名为ImageProcessing的静态类 &#xff08;3&#xff09;为Imag…

负债56亿,购买理财产品遭违约,操纵虚假粉丝,流量在下滑,客户数量减少,汽车之家面临大量风险(一)

本文由猛兽财经历时5个多月完成。猛兽财经将通过以下二十二个章节、8万字以上的内容来全面、深度的分析汽车之家这家公司。 由于篇幅限制&#xff0c;全文分为&#xff08;一&#xff09;到&#xff08;十&#xff09;篇发布。 本文为全文的第一章、第二章、第三章。 目录…

Linux的软件包管理器-yum

文章目录 软件包的概念yum源的配置的原因yum的使用查看软件包安装软件卸载软件 软件包的概念 软件包(SoftWare Package)是指具有特定的功能&#xff0c;用来完成特定任务的一个程序或一组程序。可分为应用软件包和系统软件包两大类 在Linux系统中&#xff0c;下载安装软件的方式…