JVM:运行时数据区域(白话文)

news2024/11/15 5:36:28

最近有时间在看一本<深入了解Java虚拟机>的书籍,这本书是一个中国人,名叫周志明的人写的。相比于其他翻译过来的技术书籍,这本书还是挺通俗易懂的。先前有和彬哥在聊,他说如果是自己一个人看的话会很枯燥,很难坚持下来,不妨边看边在公司内部做分享,遇到一些比较晦涩难懂的点可以收集起来和公司内部的人去讨论,大家一起学习。接下来,大家都知道,就有了这篇文章!

运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。

根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域:

1665896067755

我理解它就是JVM对运行时数据区域一个概念模型,它代表了所有虚拟机的统一外观,但各款具体的Java虚拟机并不一定要完全照着概念模型的定义来进行设计,可能会通过一些更高效率的等价方式去实现它

上图中,JVM 在线程共享与隔离的维度对JVM 内存划分为线程共享的数据区和线程隔离的数据区

那你又似曾想过,线程共享数据区为什么划分为方法区和堆?线程私有数据区划分为程序计数器、java 虚拟机栈、本地方法栈呢?

实际上,我认为线程私有内存的区域划分是参考了操作系统的进程\线程运行时的内存布局。因为操作系统中,与线程相关联的内存就包括计数器和栈区;至于线程共享的呢,我认为是根据资源的动静态属性。也就是一些相对静态的数据,例如类结构,即使编译后的代码,而像java 对象大部分会发生变更。又或者以垃圾收集的主要部分进行分区。

线程私有的数据区

程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器

为什么需要它?

由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器变量,各条线程之间计数器互不影响,独立存储。

程序计数器是内存区域唯一一个在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。

Java虚拟机栈

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

  • 局部变量表:编译期可知的各种Java虚拟机基本数据类型(boolean、int、double等)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。说的通俗一点就是用于存放方法参数和方法内部定义的局部变量

  • 操作数栈:用户方法内临时运算,暂存中间结果(2+4*5)

  • 动态链接:

    在A.java文件中,比如A.java依赖于B.java,那么在A.class的静态常量池中如何表示ClassB的地址呢,就是通过一个符号,比如字面量"abc"来表示ClassB所在的地址。 在类加载解析阶段,是有一步就是把符号引用转为直接引用的步骤。就是把"abc" 符号引用转换成classB的实际地址,也就是直接引用。实际上,我理解当时只是转换了已知的一部分,比如类的符号引用、字段的符号引用、部分方法的符号引用等。还存在一部分在类加载时也无法转换,比如ClassA某个方法中调用了某个多态的方法。多态的实现类是需要在运行时才可以确定。
    

    那如何解决这个问题?就是动态链接

    每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。

    动态链接就是将指令中的符号引用转化为真实的方法地址。

在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

《Java虚拟机规范》对本地方法栈中方法使用的语言、实现方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

线程共享的区域

Java堆

堆内存区域的唯一目的就是存放对象实例,Java世界里“几乎”所有的对象实例都在这里分配内存。

《Java虚拟机规范》中对Java堆的描述是:“所有的对象实例以及数组都应当在堆上分配

为什么堆又分为“新生代”、“老年代”?新生代又分“Eden空间”、“S0”、“S1”?

从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,实际上,这些区域划分仅仅是一部分垃圾收集器的共同特性或者说设计风格而已,而非某个Java虚拟机具体实现的固有内存布局,更不是《Java虚拟机规范》里对Java堆的进一步细致划分。

不少资料上经常写着类似于“Java虚拟机的堆内存分为新生代、老年代、永久代、Eden、Survivor……”这样的内容。在十年之前(以G1收集器的出现为分界),作为业界绝对主流的HotSpot虚拟机,它内部的垃圾收集器全部都基于“经典分代”来设计,需要新生代、老年代收集器搭配才能工作,在这种背景下,上述说法还算是不会产生太大歧义。但是到了今天,垃圾收集器技术与十年前已不可同日而语,HotSpot里面也出现了不采用分代设计的新垃圾收集器,再按照上面的提法就有很多需要商榷的地方了。

是否还存在其他角度的划分呢?

从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存

根据《Java虚拟机规范》的规定,Java堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的(虚拟内存),这点就像我们用磁盘空间去存储文件一样,并不要求每个文件都连续存放。但对于大 对象(典型的如数组对象),多数虚拟机实现出于实现简单、存储高效的考虑,很可能会要求连续的内存空间。

Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

方法区

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

《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区 分开来。

Java8 之前,Hotspot对方法区的实现为什么是永久代?又或者说如何对方法区进行回收?

在JDK 8以前,很多人都喜欢把方法区称呼为“永久代”(Permanent Generation),或将两者混为一谈。本质上这两者并不是等价的,因为仅仅是当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已,这样使得HotSpot的垃圾收集器能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作。

其他虚拟机如BEA JRockit、IBM J9 是不存在永久代的概念的。

原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一。

Hotspot为什么放弃永久代的实现

现在回头来看,当年使用永久代来实现方法区的决定并不是一个好主意,这种设计导致了Java应用更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小,而J9和JRockit只要没有触碰到进程可用内存的上限,就不会出问题(怎么理解呢?),而且有极少数方法(例如String::intern())会因永久代的原因而导致不同虚拟机下有不同的表现。当Oracle收购BEA获得了JRockit的所有权后,准备把JRockit中的优秀功能,譬如Java Mission Control管理工具,移植到HotSpot 虚拟机时,但因为两者对方法区实现的差异而面临诸多困难。考虑到HotSpot未来的发展,在JDK 6的时候HotSpot开发团队就有放弃永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划了,到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出到堆中,而到了JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta- space)来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。(放弃某一个东西不是一步到位的。)

PS:元空间和直接内存使用的都是本地内存,不受到JVM进程内存大小的限制。只受限于本机总内存

《Java虚拟机规范》对方法区的约束是非常宽松的,除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域的确是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收有时又确实是必要的

根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。

运行时常量池

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

public class cn.comein.config.RedisKeyListen extends redis.clients.jedis.JedisPubSub
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
// 比如方法中创建一个对象 new\invoke --> 可以理解是一个类的字典
   #1 = Methodref          #13.#38        // redis/clients/jedis/JedisPubSub."<init>":()V
   #2 = Fieldref           #11.#39        // cn/comein/config/RedisKeyListen.log:Lorg/slf4j/Logger;
   #3 = String             #40            // 收到redisKey过期消息,channel: {}, messageKey: {}
   #4 = InterfaceMethodref #41.#42        // org/slf4j/Logger.info:
   #5 = Class              #43            // cn/comein/common/keylisten/ExpireKeyVo
   #6 = Methodref          #5.#38         // cn/comein/common/keylisten/ExpireKeyVo."<init>":()V
   #7 = Methodref          #5.#44         // cn/comein/common/keylisten/ExpireKeyVo.setChannel:
   #8 = Methodref          #5.#45         // cn/comein/common/keylisten/ExpireKeyVo.setKey:
   #9 = Fieldref           #11.#46        // 
  #10 = InterfaceMethodref #47.#48        // org/springframework/context/ApplicationContext.publishEvent:
  #11 = Class              #49            // cn/comein/config/RedisKeyListen
  #12 = Methodref          #50.#51        // org/slf4j/LoggerFactory.getLogger:
  #13 = Class              #52            // redis/clients/jedis/JedisPubSub
  #14 = Utf8               log
  #15 = Utf8               Lorg/slf4j/Logger;
{
  public void onMessage(java.lang.String, java.lang.String);
    descriptor: (Ljava/lang/String;Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=4, args_size=3
         0: getstatic     #2                  // Field log:Lorg/slf4j/Logger;
         3: ldc           #3                  // String 收到redisKey过期消息,channel: {}, messageKey: {}
         5: aload_1
         6: aload_2
         7: invokeinterface #4,  4            // InterfaceMethod org/slf4j/Logger.info:
        12: new           #5                  // class cn/comein/common/keylisten/ExpireKeyVo
        15: dup
        16: invokespecial #6                  // Method cn/comein/common/keylisten/ExpireKeyVo."<init>":()V
        19: astore_3

既然每个class 都有一个静态常量池,那加载到jvm 时是否每一个class文件都对应一个运行时常量池?

我在网上找了一些资料,答案参其不齐,有的说不是,有的说是。后来我看了JVM 规范中对运行时常量池的描述,答案也有点模糊,不太确定,初步的答案时肯定的。

那么遇到这种情况,我就有个习惯,我会尝试站在设计者的角度去思考,也就是如果是我,我会怎么去设计、怎么去实现他!

.class文件中静态的常量池中是有索引的,每个class 的常量池都是从0开始编号,有的常量池项还持有其它常量池项的引用,指向另一个常量池项;方法指令中也存在会指向某一个常量池项。如果在类加载后都公用一个全局的运行时常量池,那么每个静态常量池中编号和索引,以及方法中的指令索引也要修改,这看来似乎不太现实。分开还更容易维护,比如在方法区中开辟一块内存专门存储运行时常量池。可以理解这一整块运行时常量池是一个map,key 是类的全限定名,value 是每个class 对应的运行时常量池。

另外,这个内存区域,一般来说,除了保存Class文件中描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中。

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是说,并不是只有Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

字符串常量池

我在学习过程中会对字符串常量池和运行时常量池所存储的东西有所混淆,不知道你们有没有。尽管你们没有,我觉得我还是有必要说一下,当作个记录笔记吧。

为什么搞一个字符串常量池?

字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。另外字符串是不可变的,可以不用担心数据冲突进行共享,所以JVM团队为了重用String对象而单独给它开辟了一块空间,叫做字符串常量池,Java1.6之前在方法区中,1.7 后移到了堆中。

注意:字符串常量池保存的是String对象的引用,String对象本身存在于堆上的其他位置。

字符串常量池在底层就是一个 StringTable,可以简单理解 map(Set),key 是该字符串的字面量,value 是指向堆中对象的引用。

很多面试官在面试的时候喜欢问这个一个问题:“String s = new String(“xyz”);创建了多少个String实例”?

很多人都会说是一个或者两个,两个的话分别是一个是字符串字面量"xyz"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与"xyz"相同的实例。

也有很多人说这样的说法不太准确,甚至认为这个问题问法有问题的。

因为根据 R大的这篇文章,在这条语句所在的类被加载时,字符串池中已经存在字符串字面量"xyz"所对应的常量池的对象了,如果是这样的话,当指令真正执行这个的时候,任何时候都是只创建了一个对象,就是 new 这个。

后来我又通过查阅资料,JVM规范里明确指定 resolve 阶段可以是lazy的。什么意思呢?就HotSpot VM的实现来说,加载类的时候,那些字符串字面量会进入到当前类的运行时常量池,不会进入全局的字符串常量池(即在StringTable中并没有相应的引用,在堆中也没有对应的对象产生),而是在执行到该行代码指令时,才开始对字符串字面量"xyz"创建对象,并把引用放入到常量池中,接着再通过new String(String)创建并初始化的、内容与"xyz"相同的实例。

从StackOverflow找到这个问题的:https://www.zhihu.com/question/55994121/answer/147296098

public class Test {
    public static void main(String[] args) {
        test('h', 'e', 'l', 'l', 'o');
    }
    static void test(char... arg) {
        String s1 = new String(arg);
      	String s2 = s1.intern();
        System.out.println('"'+s1+'"'
                +(s1!=s2? " existed": " did not exist")+" in the pool before");
        System.out.println("hello");
    }
}
// String的 intern 方法干了什么?
// DK7中,如果常量池中已经有了这个字符串,那么直接返回常量池中它的引用,如果没有,那就将它的引用保存一份到字符串常量池,然后直接返回这个引用。

直接内存

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

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

显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存大小,一般服务管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。

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

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

相关文章

智慧城市美术效果Unity实现笔记流程

智慧城市美术效果Unity实现笔记流程&#xff1a; 参考 对标 效果图&#xff1a; 写实类-参考图&#xff1a; (以上均为网络搜索效果,有落叶大师&#xff0c;以及其他优秀开发者效果图参考) 未来类-参考图&#xff1a; 如上图所示,智慧城市基本分为 这两个大类&#xff0c;偏写…

辛苦了,你身边有一批优秀下属

领导者不是全知全能的&#xff0c;假如领导者啥都会&#xff0c;还要下属有何用&#xff1f;下属还有用武之地&#xff1f; 保罗赫塞说过&#xff1a;“领导力是通过与他人合作或通过他人协作实现组织目标的过程。” 一、日行一善 我们无法靠自己完成复杂的事情&#xff0c;…

在Python中定义Main函数

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 许多编程语言都有一个特殊的函数&#xff0c;当操作系统开始运行程序时会自动执行该函数。 这个函数通常被命名为main()&#xff0c;并且依据语言标准具有特定的返回类型和参数。 另一方面&#xff0c;Python解释器从文件…

DC-7靶机

DC-7靶机地址 同样的&#xff0c;把靶机跟kali放在同一网段&#xff0c;&#xff08;NAT模式&#xff09; 主机发现 arp-scan -l端口扫描 nmap -A -T4 -p- 192.168.80.13922端口开始&#xff0c;80端口开启 浏览器先访问一下靶机的80端口 熟悉的Drupal站点 先爆破一下目录…

【Linux】详解进程状态之僵尸进程——孤儿进程

目录 &#x1f31e;专栏导读 &#x1f31b;什么是进程 ⭐什么是PCB&#xff1f; &#x1f31b;查看进程 &#x1f31b;如何通过系统调用查看进程PID &#x1f31b;fork &#x1f31e;认识进程状态 &#x1f31b;查看进程状态 &#x1f31b;R状态 ⭐例如&#xff1a…

C 语言的 ctype.h 头文件

C 语言的 ctype.h 头文件包含了很多字符函数的函数原型, 可以专门用来处理一个字符, 这些函数都以一个字符作为实参. ctype.h 中的字符测试函数如表所示: 这些测试函数返回 0 或 1, 即 false 或 true. ctype.h 中的字符映射函数如表所示: 字符测试函数不会修改原始实参, 只会…

YOLOV5改进:更换为MPDIOU,实现有效涨点!

1.该文章属于YOLOV5/YOLOV7/YOLOV8改进专栏,包含大量的改进方式,主要以2023年的最新文章和2022年的文章提出改进方式。 2.提供更加详细的改进方法,如将注意力机制添加到网络的不同位置,便于做实验,也可以当做论文的创新点。 2.涨点效果:更换为MPDIOU,实现有效涨点! 目录…

C++代码生成静态LIB链接库及其调用方法

1、在进行C代码移植时可将CPP文件封装为静态lib链接库&#xff0c;本文章讲述了如何将cpp文件封装为lib文件。 2、假设有文件a.cpp、a.h、b.cpp、b.h以及main.cpp&#xff0c;假设main调用了b&#xff0c;b调用了a。现在需要将a.cpp以及b.cpp封装为a.lib以及b.lib。 3、在VS2…

Java8中forEach()里使用return的效果

先总结&#xff1a;使用forEach()处理集合时不能使用break和continue这两个方法&#xff0c;可以使用无返回值的return跳出此次循环&#xff0c;效果同标准for循环的continue。 首先&#xff0c;forEach()先对入参判空&#xff0c;然后使用增强for循环调用action.accept(t)&am…

VGG16模型详解

VGG16模型详解 0、VGG16介绍 VGG16是一种深度卷积神经网络&#xff0c;由牛津大学的研究团队于2014年开发。 VGG16在2014年的ImageNet Large Scale Visual Recognition Challenge (ILSVRC) 竞赛中取得了显著的成绩。它在图像分类任务中获得了当年的第二名&#xff0c;其准确…

【Java可执行命令】(二十一)线程快照生成工具 jstack:帮助开发人员分析和排查线程相关问题(死锁、死循环、线程阻塞...)

Java可执行命令之jstack 1️⃣ 概念2️⃣ 优势和缺点3️⃣ 使用3.1 语法格式3.2 使用步骤及技巧3.3 使用案例 4️⃣ 应用场景&#x1f33e; 总结 1️⃣ 概念 jstack 命令是 Java Development Kit&#xff08;JDK&#xff09;中提供的一项诊断工具&#xff0c;用于生成Java虚拟…

震坤行工业超市旗下震坤行智能制造(苏州)有限公司开工奠基仪式圆满成功

震坤行工业超市旗下震坤行智能制造&#xff08;苏州&#xff09;有限公司开工奠基仪式圆满成功 2023年7月3日&#xff0c;震坤行工业超市于太仓港经济技术开发区举行了震坤行智能制造&#xff08;苏州&#xff09;有限公司项目奠基动工仪式。震坤行董事长兼CEO陈龙&#xff0c…

基于OFDM通信系统的低复杂度的资源分配算法matlab性能仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .......................................................................%子载波分配[~,po…

Grafana技术文档-概念-《十分钟扫盲》

Grafana官网链接 Grafana: The open observability platform | Grafana Labs 基本概念 Grafana是一个开源的度量分析和可视化套件&#xff0c;常用于对大量数据进行实时分析和可视化。以下是Grafana的基本概念&#xff1a; 数据源&#xff08;Data Source&#xff09;&#…

idea+gradle阅读spring5.2.9源码之源码构建报错解决方案

注意 1、先确保gradle版本和spring、jdk版本对应 本文:gradle:5.6.4/spring 5.2.9/jdk1.8&#xff08;gradle和jdk都要先安装好&#xff0c;gradle还要配置好本地资源文件路径&#xff09; 2、原来项目乱了的话&#xff0c;先重新导入下载的源码项目 3、进入源码所在根目录&…

【iOS】autoreleasepool

来说一下最近在了解的autoreleasepool吧&#xff0c;我们可能平时书写过许多脑残代码&#xff0c;其有很多的缺陷但是我们可能当时学的比较浅就也不太了解&#xff0c;就像下面这样的&#xff1a; for (int i 0; i < 1000000; i) {NSNumber *num [NSNumber numberWithInt…

【前端 | CSS】aligin-items与aligin-content的区别

align-items 描述 CSS align-items 属性将所有直接子节点上的 align-self 值设置为一个组。align-self 属性设置项目在其包含块中在交叉轴方向上的对齐方式 align-items是针对每一个子项起作用&#xff0c;它的基本单位是每一个子项&#xff0c;在所有情况下都有效果&…

【uniapp】原生子窗体subNvue的使用与踩坑

需求 最近接到个需求, 需要在video组件上弹出弹窗, 也就是覆盖video这个原生组件 未播放时, 弹窗可以覆盖, 但是当video播放时, 写的弹窗就覆盖不了了 因为video是原生组件, 层级非常高, 普通标签是覆盖不了的, map标签同理 覆盖原生组件, 官方给出解决办法一. 使用cover-view…

文件传输软件常见问题解决办法大全

文件传输软件是我们工作中不可缺少的一种工具&#xff0c;它可以帮助我们快速、安全、稳定地传输各种文件&#xff0c;如文档、图片、视频等。但是在使用文件传输软件的过程中&#xff0c;我们也可能会遇到一些问题&#xff0c;影响我们的工作效率和传输质量。那么&#xff0c;…

【陈老板赠书活动 - 10期】- 【Python之光:Python编程入门与实战】

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;赠书活动专栏&#xff08;为大家争取的福利&#xff0c;免费送书&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。讲一些我刚进公司的学…