第3章 垃圾收集器与内存分配策略《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》
第3章 垃圾收集器与内存分配策略
3.2 对象已死
Java世界中的所有对象实例,垃圾收集器进行回收前就是确定对象哪些是活着的,哪些已经死去。
3.2.1 引用计数算法
常见的回答是:给对象中添加一个引用计数器,有地方引用,计数器+1,引用失效,计数器-1,任何时刻计数器为零的对象就是不可能再被使用了。
引用计数算法(Reference Counting)占了一些额外内存空间来计数,但原理简单,判定效率高,大多数下也是一个好的算法。
Java领域主流虚拟机都没用引用计数算法,原因为看似简单,很多例外情况要考虑,要配合大量额外处理才能保证正确地的工作,例如很难解决对象之间相互循环引用的问题。
引用计数算法陷阱
package a.b.c;/*** */
public class ReferenceCountingGC {public Object instance = null;private static final int _1MB = 1024 * 1024;/*** 意义就是占点内存,以便能在GC日志中清楚是否有回收过*/private byte[] bigSize = new byte[2 * _1MB];public static void testGC() {ReferenceCountingGC objA = new ReferenceCountingGC();ReferenceCountingGC objB = new ReferenceCountingGC();objA.instance = objB;objB.instance = objA;objA = null;objB = null;System.out.println("---------进行GC!---------");//假设在这行发生GC,objA和objB是否能被回收?System.gc();}public static void main(String[] args) {testGC();}}
证明虚拟机没有因为两个对象互相引用就放弃回收它们。
所以Java虚拟机不是通过引用计数算法判断对象是否存活的。
3.2.2 可达性分析算法
Java通过可达性分析(Reachability Analysis)算法来判定对象是否存活。
基本思路:通过一些列"GC Roots"的根对象作为起始节点集,根据引用关系向下搜索,搜索过程所走过的路径称为"引用链(Reference Chain)",如果某对象到GC Roots间没有任何引用链相连,说明这个对象不可达,证明此对象不可能再被使用。
Java技术体系里,固定可作为GC Roots的对象包括以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象,各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 方法区中,类静态属性引用的对象。
- 方法区中,常量引用的对象,字符串常量池(String Table)里的引用。
- 本地方法栈中JNI(Native方法)引用的对象。
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,常驻的异常对象NullPointException,OutOfMemoryError等,系统类加载器。
- 被同步锁(synchronized关键字)持有的对象。
- 反映Java虚拟机内部情况的JMXBean,JVMTI中注册的回调、本地代码缓存等。
3.2.3 再谈引用
Java引用的传统定义:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称该reference数据是代表某块内存,某个对象的引用。
更广义的讲,Java对引用的概念进行了扩充,将引用分为**强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)**4种,引用强度依次减弱。
- 强引用是最传统的引用,指在程序代码中普遍存在的引用赋值,类似"Object obj = new Object()",这种引用关系,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
- 软引用用来描述一些还有用,但非必须得对象,只被软引用关联着的对象在系统发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收,如果这次回收还没有足够内存,会抛出内存溢出异常,JDK1.2之后提供SoftReference类来实现软引用。
- 弱引用也是用来描述非必须对象,强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生为止,当垃圾收集器开始工作,无论当前内存是否足够,都会回收弱引用关联的对象,提供WeakReference类来实现。
- 虚引用也称为幽灵引用或幻影引用,最弱的引用关系,一个对象是否有虚引用存在不会对其生成时间构成影响,也无法通过虚引用获取对象实例,唯一目的是为了能在这个对象被收集器回收时收到一个系统通知,PhantomReference类实现。
3.2.4 生存还是死亡?
可达性分析算法判定为不可达对象也不似非死不可,这时候处于缓刑,宣告对象死亡要经历两次标记过程:对象可达性分析后没有与GC Roots相连接的引用链,第一次标记,对象没有覆盖finalize()方法或此方法已经被虚拟机调用过,虚拟机将这两种情况视为"没有必要执行"。
对象被判定为执行finalize()方法,会被放到F-Queue队列中,由虚拟机自动建立,低调度优先级的Finalizer线程去执行它们的finalize()方法。
最后一次逃脱机会是在F-Queue队列的对象被收集器第二次小规模标记,若对象重新与引用链连接上,第二次标记会溢出即将回收的集合,若没有逃脱,那么就要回收了。
一次对象的自我拯救演示
package a.b.c;public class FinalizeEscapeGC {public static FinalizeEscapeGC SAVE_HOOK = null;public void isAlive() {System.out.println("yes,i am still alive 0.0");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");FinalizeEscapeGC.SAVE_HOOK = this;}public static void main(String[] args) throws InterruptedException {SAVE_HOOK = new FinalizeEscapeGC();//对象第一次成功拯救自己SAVE_HOOK = null;System.gc();//因为Finalizer方法优先级很低,暂停0.5秒,等待它Thread.sleep(500);if(SAVE_HOOK!=null) {SAVE_HOOK.isAlive();} else {System.out.println("no,i am dead *.*");}//下面这段代码与上面完全相同,但这次自救却失败了SAVE_HOOK = null;System.gc();//因为Finalizer方法优先级很低,暂停0.5秒,等待它Thread.sleep(500);if(SAVE_HOOK!=null) {SAVE_HOOK.isAlive();} else {System.out.println("no,i am dead *.*");}}}
两段完全一样的代码执行结果一次逃脱成功,一次失败,因为finalize()方法只会被系统自动调用一次,第二次不执行了,第二段代码自救失败了。
finalize()方法不推荐使用,把它忘了吧。
3.2.5 回收方法区
方法区垃圾收集性价比低,主要回收:废弃的常量和不再使用的类型,如没有任何一个字符串对象引用常量池中的"java"常量,虚拟机中其他地方也没引用,则它会被系统清理出常量池。
不是使用的类,判定起来比较苛刻:
- 该类以及子类所有实例已经被回收。
- 加载该类的类加载器已经被回收。
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
也仅仅是被允许,提供了一系列参数控制类的卸载,自己查吧。
大量使用反射,动态代理,CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景,通常都需要具备Java虚拟机类型卸载的能力,保证不会对方法区造成过大的内存压力。
3.3 垃圾回收算法
理论细节参考《垃圾回收算法手册》2~4章内容。
垃圾回收算法可划分为:**引用计数式垃圾收集(Reference Counting GC)和追踪式垃圾收集(Tracing GC)**两大类。
主流Java虚拟机都采用的是追踪式垃圾收集。
3.3.1 分代收集理论
当前商业虚拟机的垃圾收集器,大多数遵循"分代收集"(Generational Collection)的理论进行设计。是程序的经验法则,建立在两个分代假说之上:
1.弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕死。
2.强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难消亡。
这两个假说奠定了垃圾收集器的一致设计原则:
收集器将Java堆划分出不同的区域,将回收对象依据年龄(熬过垃圾收集的次数)分配到不同的区域之中存储。大多数朝生夕死的对象放在一起,每次回收只关注少量存活的对象,而不去标记大量要被回收的对象,以低频率来回收这个区域,同时兼顾了垃圾收集的时间开销和内存空间的有效利用。
Java堆划分区域后,每次只回收某一个或某部分区域,也就有了**“Minor GC,新生代垃圾回收”,“Major GC,老年代垃圾回收”,“Full GC”**这些回收类型的划分,也就有了针对不同区域与存储对象存亡特征相匹配的垃圾收集算法,“标记-清除算法”,“标记-整理算法”,“标记清除算法”。Minor:/ˈmaɪnər/未成年,少数的,次要的,Major:/ˈmeɪdʒər/主要的,大的。
一般至少会把堆分为"新生代(Young Generation)"和**老年代(Old Generation)**两个区域。
新生代中,每次垃圾收集都发现大批对象死去,每次回收后存活的少量对象都会逐步晋升到老年代中存放。分代收集并非简单划分内存区域那么简单,至少有1条,对象不是孤立的,对象之间会存在跨代引用。
假设进行新生代区域的收集(Minor GC),新生代对象可能被老年代所引用,为找出新生代中的存活对象,不得不在固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析结果的正确性,反过来也一样。遍历老年代所有对象可行,但为内存回收带来很大的性能负担,为解决这个问题,添加第三条经验法则:
3.跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。
隐含推论:存在互相引用关系的两个对象,应该倾向于同时生存或者同时消亡的。
如某新生代对象引用老年代对象,老年代对象难以消亡,新生代对象得以存活,进而年龄增长后晋升到老年代中,这时跨代引用也随之消除了。
根据第3假说,不应该为少量跨代引用去扫描整个老年代,也不必浪费空间记录每个对象是否存在跨代引用,只需再新生代上建立全局的数据结构(记忆集,Remembered Set),此结构把老年代划分若干小块,标识老年代哪块内存存在跨代引用,发生MinorGC时,跨代引用小块的对象才会被加入到GC Roots进行扫描。改变引用关系维护记录数据的正确性会增加一点开销,但是比起扫描整个老年代来说还是划算的。
收集分类
- 部分收集(Partial GC):指目标不是完整收集整个Java堆,而是收集部分,又分为:
- 新生代收集(Minor GC/Young GC):只收集新生代的垃圾收集。
- 老年代收集(Major GC/Old GC):只是老年代的垃圾收集。目前只有CMS会单独收集老年代的行为,另外注意,Major GC说法有混淆,根据上下文看到底是指老年代收集还是整堆收集。
- 混合收集(Mixed GC):
- 整堆收集(Full GC):收集整个Java堆的方法区的垃圾收集。
3.3.2 标记-清除算法
最基础的垃圾收集算法,**标记-清除(Mark-Sweep)**算法。
算法分为两阶段:
- 标记阶段:标记所有需要回收的对象。
- 清除阶段:标记完成后回收掉所有被标记的对象。
缺点有两个:
- 执行效率不稳定,堆中包含大量对象,大部分需要被回收,要进行大量标记和清除动作,两个动作的执行效率随对象数量增长而降低。
- 内存碎片化问题,清除后产生大量不连续的内存碎片,碎片太多当程序需要分配大对象时无法找到连续的内存而不得不提前触发一次垃圾收集动作。
3.3.3 标记-复制算法
简称为复制算法,解决了大量可回收对象执行效率低的问题。
最早提出的了"半区复制(Semispace Copying)",将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,一块用完,将存活的对象复制到另外一块上面,把已使用过的内存空间一次清理掉。
优点:不用考虑内存碎片,分配内存时移动堆顶的指针,按顺序分配即可实现简单,运行高效。
缺点:可用内存缩小为原来的一半,空间浪费太多。
现在的Java虚拟机优化了这种收集算法。
新生代中的对象98%熬不过第一轮收集,因此不需要1:1的比例来划分新生代的内存空间。
更优化的半区复制分代策略,称为"Appel式回收"。
HotSpot虚拟机的Serial、ParNew等新生代收集器采用这种策略。
新生代内存区域,每次使用Eden+其中1个Survivor,共计90%内存区域。
- Eden,80%
- Survivor0,10%
- Survivor1,10%
老年代内存区域
若上面Minor GC垃圾回收后,复制到Survivor中的对象超过10%,触发了分配担保(Handle Promotion),将这些对象直接进入到老年代,这是安全的。
3.3.4 标记-整理算法
复制算法中,对象存活率高的极端情况,复制操作多,效率低,而且不想浪费50%内存就要进行分配担保,用来应对100%对象存活的极端情况,所有老年代一般不能直接选用这种算法。
根据老年代对象活的久的特点,提出了"标记-整理算法(Mark-Compact)".
标记过程:还是标记-清除算法。
整理过程:将所有存活的对象向内存空间一端移动,然后直接清理边界以外的内存。
移动存活的对象是极重的负担, 操作过程要暂停用户应用程序才能进行。
Stop The World,全局停止,世界停止,这个系统开销也了不得。
如果不考虑移动和整理的话,空间碎片化问题就要用"分区空闲分配链表"来解决,增加额外负担,影响程序的吞吐量。
基于以上了两点,是否移动对象都有弊端,移动则内存回收复杂,不移动则内存分配复杂。
有一种折中的方法,虚拟机平时多数时间采用标记-清除算法,暂时容忍碎片,知道内存碎片大到影响对象分配时,再采用标记整理算法收集一次,以获得规整的内存空间,基于标记-清除算法CMS收集器面临的空间碎片过多时就采用这种办法。
3.4 HotSpot的算法细节实现
较枯燥,可先看垃圾收集器。
3.5 经典垃圾收集器
如果说收集算法是内存回收的方法论,那垃圾收集器就是内存回收的实践者。
规范没有对垃圾回收器的实现做规定。
各款经典收集器之间的关系图:
展示了7种作用于不同分代的收集器,两个收集器之间存在连线,就说明它们可以搭配使用。
收集器所处的区域标识它们属于新生代收集器或是老年代收集器(Tenured,终身的,长期保有的)。
明确一个观点:各个收集器的比较不是挑选最好的收集器出来,没出现最好的收集器,更不存在万能收集器,只是选择最合适的收集器。
新生代垃圾回收器:Serial,ParNew,ParallelScavenge
老年代垃圾回收器:CMS,Serial Old(MSC),Parallel Old
3.5.1 Serial收集器
Serial/ˈsɪəriəl/连续的,排成顺序的。
最基础,历史最悠久,JDK1.3.1之前是新生代收集器的唯一选择。
单线程工作的收集器。
不仅仅使用一个处理器或一条收集线程去完成垃圾收集工作,进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
会"Stop The World",在用户不可知的情况下,把正常工作的线程全部停掉,这无法接受。
下图Serial/Serial Old收集器的运行过程。
给用户带来恶劣体验,早起设计者们完全理解,但也很委屈,你妈妈给你打扫房间的时候,肯定会让你老老实实的在椅子上或者房间外待着,如果她一边打扫,一边扔纸屑,这房间还能打扫完?,这也合情合理。
看似老而无用,实则依然是HotSpot运行在客户端模式下默认的新生代收集器。
优点:简单而高效,内存受限的情况下,内存额外消耗最小,对于单核或处理器核心少的环境来说,没有线程交互的开销,尤其微服务应用中,分配内存小,收集几十到一两百兆新生代,垃圾回收停顿时间可以控制在十几、几十毫秒,最多100毫秒以内,只要不频繁发生,这些停顿时间可以接受。
缺点:单线程,暂停世界导致全部工作线程频繁停顿,不适合在大内存虚拟机中使用。
3.5.2 ParNew收集器
ParNew是Serial收集器的多线程并行版本。
除了使用多条线程进行垃圾收集之外,其余包括Serial可用的所有控制参数
-XX:SurvivorRatio,ratio(比例),SurvivorRatio 指的是幸存区比例。在垃圾回收机制中,通常会有新生代和老年代等不同的内存区域划分,而新生代中又有 Eden 区和两个 Survivor 区。SurvivorRatio 这个参数用于设置 Eden 区和 Survivor 区的大小比例关系。例如,如果设置为 8,则表示 Eden 区和 Survivor 区的大小比例为 8:1:1。
-XX:PretenureSizeThreshold,中文意思是 “晋升到老年代的大小阈值”。在 Java 虚拟机中,这个参数用于设置对象从新生代晋升到老年代之前的大小阈值。如果对象的大小超过这个阈值,就会直接晋升到老年代,而不是在新生代中经历多次垃圾回收。这个参数可以帮助优化垃圾回收的性能,根据不同的应用场景进行调整。例如,对于创建大型对象较多的应用,可以适当调大这个阈值,以减少新生代的垃圾回收次数。一般来说,大对象是32K,也有1M,或2M。
-XX:HandlerPromotionFailure,垃圾回收(GC)过程中的对象晋升失败相关,原因有老年代空间不足,老年代内存碎片放不下晋升的对象,大对象直接分配给老年代导致老年代空间不足。老年代还未充分整理,新生代对象就晋升为老年代,导致空间不足,JVM设置的老年代太小导致无法满足晋升的需求。
收集算法,暂停世界,对象分配规则等与Serial收集器完全一致,共用很多代码。
ParNew收集器工作过程:
是JDK7之前遗留系统中首选的新生代收集器。
有一个重要原因:除Serial收集器外,只有它能与CMS收集器配合工作。
ParNew是激活CMS后的默认新生代收集器,也只能相互搭配使用,不能和其他收集器配合了。
- 并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条线程在协同工作,通常默认此时用户线程是出于等待状态。
- 并发(Concurrent):并发描述的垃圾收集器线程和用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。
3.5.3 Parallel Scavenge(清理)收集器
也是新生代收集器,基于标记复制算法,能够并行收集的多线程收集器。
特点与其他不同,CMS等关注点是缩短用户线程的停顿时间。
而它则是达到一个可控制的吞吐量(Throughput)。
$ 吞吐量=\frac{运行用户代码时间}{运行用户代码时间+运行垃圾收集时间} $
如果虚拟机完成某个任务,总共耗费100分钟,垃圾收集花费1分钟,吞吐量就是99%。
Parallel Scavenge收集器提供两个参数用于精确控制吞吐量:
最大垃圾收集停顿时间:-XX:MaxGCPauseMillis:大于0的毫秒数,越小,频率越高
直接设置吞吐量大小:-XX:GCTimeRatio:0<整数<100,默认99,则允许1%,19则是5%。
也经常被称为"吞吐量优先收集器"
自适应调节策略(GC Ergonomics)也是Parallel Scavenge收集器区别于ParNew收集器的重要特征,把内存管理的调优任务交给虚拟机完成,把内存数据设置好-Xmx。
3.5.4 Serial Old收集器
是Serial收集器的老年代版本,同样是一个单线程收集器,使用标记复制算法。
JDK5之前与Parallel Scavenge收集器搭配使用。
作为CMS收集器发生失败时的后备预案。
3.5.5 Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,标记-整理算法。
3.5.6 CMS收集器
CMS(Concurrent Mark Sweep)是以获取最短回收停顿时间为目标的收集器。
B/S架构更关注服务的响应速度,希望系统停顿时间更短,CMS更符合要求。
基于标记-清除算法实现的:
- 初始标记(CMS initial mark),标记GC Roots关联到的对象,速度很快,会Stop The World。
- 并发标记(CMS concurrent mark),通过GC Roots的直接关联对象遍历整个对象图的过程,可与用户线程并发。
- 重新标记(CMS remark),修正并发标记期间用户继续运行导致的标记产生变动的对象,会STW
- 并发清除(CMS concurrent sweep),并发删除掉已经判断死亡的对象,可与用户线程并发。
优点:并发收集、低停顿。
缺点:1.对处理器资源敏感,核心4个以上,并发回收线程不超过25%,2.无法处理浮动垃圾(新产生的垃圾),3.空间碎片多,空间不够分配对象会Full GC。
3.5.7 Garbage First收集器
G1,全功能垃圾回收器,服务端默认回收器。
开创Region布局,遵循分代理论,但是堆内存划分为大小相等的独立区域(Region),每个区域可以根据需要扮演新生代Eden,Survivor,老年代。
Region中超过容量(1-32MB,为2的幂,通常2MB)的一半判定为大对象,存放在多个Humongous Region块中,把它看成老年代。
6-8GB内存以上,G1表现更好,否则CMS。
3.6 低延迟垃圾收集器
垃圾收集器三项最重要指标:内存占用(Footprint)、吞吐量(Throughtput)、延迟(Latency)。
不可能三角,最多占两个。延迟是最重要的指标。
3.6.1 Shenandoah收集器(谢南多厄)
同G1相似,基于Region堆,放大对象Humongous Region,优先处理回收价值最大的Region。
不同点:支持并发整理算法,回收阶段以多线程并行,却不能与用户线程并发。不分代。
工作过程分为9个阶段:
等待。。。
3.6.2 ZGC收集器
JDK11加入,低延迟垃圾收集器。垃圾收集停顿时间限制在10毫秒。
也采用Region堆内存布局:
待定。。。
3.7 选择合适的垃圾收集器
3.8 实战:内存分配与回收策略
3.8.1 对象优先在Eden分配
3.8.2 大对象直接进入老年代
3.8.3 长期存活的对象将进入老年代
3.8.4 动态对象年龄判定
相关文章:
第3章 垃圾收集器与内存分配策略《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》
第3章 垃圾收集器与内存分配策略 3.2 对象已死 Java世界中的所有对象实例,垃圾收集器进行回收前就是确定对象哪些是活着的,哪些已经死去。 3.2.1 引用计数算法 常见的回答是:给对象中添加一个引用计数器,有地方引用࿰…...
MCP是什么?为什么突然那么火?
什么是MCP? MCP全称为Model Context Protocol(模型上下文协议),是由Anthropic公司在2024年11月推出的一个开源协议。Anthropic是一家以其开发的Claude大语言模型而闻名的公司。MCP旨在提供一个通用的开放标准,以简化大型语言模型…...
与终端同居日记:Linux指令の进阶撩拨手册
前情提要: 当你和终端的关系从「早安打卡」进阶到「深夜代码同居」,那些曾经高冷的指令开始展露致命の反差萌—— man 是那个永远在线的钢铁直男说明书,只会说:"想懂我?自己看文档!"(…...
STM32单片机入门学习——第42节: [12-2] BKP备份寄存器RTC实时时钟
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.04.19 STM32开发板学习——第42节: [12-2] BKP备份寄存器&RTC实时时钟 前言开发板说…...
AI 驱动抗生素发现:从靶点到化合物测试
AI 驱动抗生素发现:从靶点到化合物测试 目录 基于 AI 驱动的研发流程发现抗生素,整合靶点选择和深度学习分子生成,显著提升了候选药物发现效率。结合数据平衡技术,机器学习和 AutoML 能有效提升不平衡数据集分类性能。RibbonFold 是一种新的 AI 模型,可以准确预测淀粉样蛋…...
群晖威联通飞牛等nas如何把宿主机硬盘挂接到可道云docker容器中
可道云系统是用户常用的一款面向个人用户的轻量级私有云存储工具,以高效管理和安全存储为核心,打造便捷的数字化办公体验。但是用户希望把原有其他磁盘中文件挂接到这个新系统中有很大的难度,主要是对linux文件系统理解有很大的误区,认为目录结构是固定的…...
用 R 语言打造交互式叙事地图:讲述黄河源区生态变化的故事
目录 🌟 项目背景:黄河源头的生态变迁 🧰 技术栈介绍 🗺️ 最终效果预览 💻 项目构建步骤 1️⃣ 数据准备 2️⃣ 构建 Leaflet 地图 3️⃣ 使用 scrollama 实现滚动触发事件 4️⃣ 使用 R Markdown / Quarto 打包发布 🎬 效果展示截图 📦 完整代码仓库 …...
opencv(双线性插值原理)
双线性插值是一种图像缩放、旋转或平移时进行像素值估计的插值方法。当需要对图像进行变换时,特别是尺寸变化时,原始图像的某些像素坐标可能不再是新图像中的整数位置,这时就需要使用插值算法来确定这些非整数坐标的像素值。 双线性插值的工…...
Flutter 弹窗队列管理:实现一个线程安全的通用弹窗队列系统
在开发复杂的 Flutter 应用时,弹窗的管理往往是一个令人头疼的问题。尤其是在多个弹窗需要按顺序显示,或者弹窗的显示需要满足特定条件时,手动管理弹窗的显示和隐藏不仅繁琐,还容易出错。为了解决这个问题,我们可以实现…...
Linux压缩与解压命令完全指南:tar.gz、zip等格式详解
Linux压缩与解压命令完全指南:tar.gz、zip等格式详解 在Linux系统中,文件压缩和解压是日常操作中不可或缺的一部分。本文将全面介绍Linux下常用的压缩和解压命令,包括tar.gz、tar、zip等格式的区别和使用方法,帮助你高效管理文件…...
doris/clickhouse常用sql
一、doris常用SQL 1、doris统计数据库的总大小(单位:MB) SELECT table_schema AS database_name,ROUND(SUM(data_length) / 1024 / 1024, 2) AS database_size_MB FROM information_schema.tables WHERE table_schema NOT IN (information…...
实现AWS Lambda函数安全地请求企业内部API返回数据
需要编写一个Lambda函数在AWS云上运行,它需要访问企业内部的API获取JSON格式的数据,企业有网关和防火墙,API有公司的okta身份认证,通过公司的域账号来授权访问,现在需要创建一个专用的域账号,让Lambda函数访…...
【Easylive】Interact与Web服务调用实例及网关安全拦截机制解析
【Easylive】项目常见问题解答(自用&持续更新中…) 汇总版 easylive-cloud-interacteasylive-cloud-web 1. 不同服务(web和interact)之间的调用方式 调用流程 • 角色分工: • easylive-cloud-web:作…...
【HDFS】EC重构过程中的校验功能:DecodingValidator
一、动机 DecodingValidator是在HDFS-15759中引入的一个用于校验EC数据重构正确性的组件。 先说下引入DecodingValidator的动机,据很多已知的ISSUE(如HDFS-14768, HDFS-15186, HDFS-15240,这些目前都已经fix了)反馈, EC在重构的时候可能会有各种各样的问题,导致数据错误…...
Chromium 134 编译指南 macOS篇:编译优化技巧(六)
1. 引言 在Chromium 134的开发过程中,优化编译速度是提升开发效率的关键因素。本文将重点介绍如何使用ccache工具来加速C/C代码的编译过程,特别是在频繁切换分支和修改代码时。通过合理配置和使用这些工具,您将能够显著减少编译时间…...
FPGA——基于DE2_115实现DDS信号发生器
FPGA——基于DE2_115实现DDS信号发生器 文章目录 FPGA——基于DE2_115实现DDS信号发生器一、实验要求二、实现过程(1)新建工程 二、波形存储器ROM(1)方波模块(2)正弦波形存储器(3)锁…...
PHP中的ReflectionClass讲解【详细版】
快餐: ReflectionClass精简版 在PHP中,ReflectionClass是一个功能强大的反射类,它就像是一个类的“X光透视镜”,能让我们在程序运行时深入了解类的内部结构和各种细节。 一、反射类的基本概念和重要性 反射是指在程序运行期间获…...
嵌入式面试题解析:常见基础知识点详解
在嵌入式领域的面试中,基础知识点的考察尤为重要。下面对一些常见面试题进行详细解析,帮助新手一步步理解。 一、原码、反码、补码及补码的好处 题目 什么叫原码、反码、补码?计算机学科引入补码有什么好处? 在计算机科学中&a…...
GPU渲染阶段介绍+Shader基础结构实现
GPU是什么 (CPU)Center Processing Unit:逻辑编程 (GPU)Graphics Processing Unit:图形处理(矩阵运算,数据公式运算,光栅化) 渲染管线 渲染管线也称为渲染流水线&#x…...
08-DevOps-向Harbor上传自定义镜像
harbor创建完成,往harbor镜像仓库中上传自定义的镜像,包括新建项目、docker配置镜像地址、镜像重命名、登录harbor、推送镜像这几个步骤,具体操作如下: harbor中新建项目 访问级别公开,代表任何人都可以拉取仓库中的镜…...
C++学习之路,从0到精通的征途:vector类的模拟实现
目录 一.vector的介绍 二.vector的接口实现 1.成员变量 2.迭代器 (1)begin (2)end 3.容量操作 (1)size,capacity (2)reserve (3)resize…...
嵌入式软件--stm32 DAY 2
大家学习嵌入式的时候,多多学习用KEIL写代码,虽然作为编译器,大家常用vscode等常用工具关联编码,但目前keil仍然是主流工具之一,学习掌握十分必要。 1.再次创建项目 1.1编译器自动生成文件 1.2初始文件 这样下次创建新…...
多模态大语言模型arxiv论文略读(二十九)
Temporal Insight Enhancement: Mitigating Temporal Hallucination in Multimodal Large Language Models ➡️ 论文标题:Temporal Insight Enhancement: Mitigating Temporal Hallucination in Multimodal Large Language Models ➡️ 论文作者:Li Su…...
【人工智能学习-01-01】20250419《数字图像处理》复习材料的word合并PDF,添加页码
前情提要 20250419今天是上师大继续教育人工智能专升本第一学期的第一次线下课。 三位老师把视频课的内容提炼重点再面授。(我先看了一遍视频,但是算法和图像都看不懂,后来就直接挂分刷满时间,不看了) 今天是面对面授…...
B端APP设计:打破传统限制,为企业开启便捷新通道
B端APP设计:打破传统限制,为企业开启便捷新通道 在数字化转型浪潮中,企业级移动应用正突破传统管理系统的功能边界,演变为连接产业链各环节的核心枢纽。本文从技术架构革新、交互模式进化、安全防护升级三个维度,系统…...
【多线程5】面试常考锁知识点
文章目录 悲观/乐观锁挂起等待锁/自旋锁偏向锁轻量级/重量级锁锁升级CASCAS引发的ABA问题解决方案 原子类 公平/不公平锁可重入锁ReentrantLock读写锁 Callable接口 这里的“悲观”“乐观”“挂起等待”“自旋”“轻量级”“重量级”“公平”“非公平”“可重入”仅代表某个锁的…...
Linux第一个系统程序——进度条
1.回车与换行 回车(CR, \r): 作用:将光标移动到当前行的行首(最左侧),但不换到下一行。 历史来源:源自打字机的“回车”操作——打字机的滑架(Carriage)需…...
C 语 言 --- 指 针 3
C 语 言 --- 指 针 3 函 数 指 针函 数 指 针 数 组代 码 解 释回 调 函 数 - - - qsort模 拟 实 现 qsort 函 数 总结 💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 C 语 言 💡个 人 主 页:笑口常开x…...
蓝桥杯之递归
1.数字三角形 题目描述 上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和(路径上的每一步只可沿左斜线向下或右斜线向下走)。 输…...
学习笔记十八——Rust 封装
🧱 Rust 封装终极指南:结构体、模块、Trait、目录结构与模块引用 🧭 目录导航 什么是封装?Rust 的封装理念Rust 的封装工具总览模块(mod)和访问控制(pub)详解结构体和枚举ÿ…...
【面试向】点积与注意力机制,逐步编码理解自注意力机制
点积(dot product)两个向量点积的数学公式点积(dot product)与 Attention 注意力机制(Attention)注意力机制的核心思想注意力机制中的缩放点积自注意力机制中,谁注意谁? 逐步编码理解…...
基础数学知识-线性代数
1. 矩阵相乘 c i j = a i k ∗ b k j c_{ij} = a_{ik} * b_{kj} cij=aik∗bkj 1. 范数 1. 向量的范数 任意一组向量设为 x ⃗ = ( x 1 , x 2 , . . . , x N ) \vec{x}=(x_1,x_2,...,x_N) x =(x1,x2,...,xN) 如下: 向量的1范数: 向量的各个元素的绝对值之和∥ …...
【KWDB 创作者计划】_上位机知识篇---Docker容器
文章目录 前言1. Docker 容器是什么?隔离性轻量级可移植性可复用性 2. Docker 核心概念镜像容器仓库Dockerfile 3. Docker 基本使用(1) 安装 Docker(2) 容器生命周期管理(3) 镜像管理(4) 进入容器内部(5) 数据持久化(挂载卷)(6) 网络管理 4. …...
指针函数和函数指针
指针函数本质是一个函数,只是函数的返回值是指针类型 函数指针本质是一个指针,只是这个指针指向的是一个函数 指针函数 函数有很多类型的返回值,例如 short funcA(参数列表) // 表示该函数返回值是一个short类型 void funcA(参数列表) // 表…...
案例驱动的 IT 团队管理:创新与突破之路:第六章 组织进化:从案例沉淀到管理体系-6.1 案例库建设方法论-6.1.2案例分级与标签体系
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 案例分级与标签体系构建方法论:IT团队知识管理的结构化实践1. 案例库建设的战略价值与核心挑战1.1 案例管理的战略定位1.2 分级标签体系的核心价值 2. 案例分级体…...
sqlilabs-Less之HTTP头部参数的注入——基础篇
Less-18 user-agent报错注入 这一关的代码漏洞点出现在了insert语句,因为这里没有对user-agent和ip_address进行过滤,,并且输出了mysql的错误信息 补充知识点 PHP里用来获取客户端IP的变量 $_SERVER[HTTP_CLIENT_IP] #这个很少使用…...
java多线程相关内容
java线程创建的方式 一共有四种方式 继承 Thread 类:本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例 启动线程的唯一方 法就是通过 **Thread 类的 start()**实例方法。start()方法是一个 native 方法,它将启动一个新线 程&…...
Windows Server .NET Core 应用程序部署到 IIS 解决首次访问加载慢的问题
第一篇: Windows .NET Core 应用程序部署到 IIS 解决首次访问加载慢的问题 第二篇:Windows Server .NET Core 应用程序部署到 IIS 解决首次访问加载慢的问题 第三篇:Windows .NET Core 应用程序部署到 IIS 解决首次访问加载慢的问题 设置…...
ubuntu24.04上使用qemu+buildroot+uboot+linux+tftp+nfs模拟搭建vexpress-ca9嵌入式linux开发环境
1 准备工作 1.1 安装依赖工具 sudo apt-get update && sudo apt-get install build-essential git bc flex libncurses5-dev libssl-dev device-tree-compiler1.2 安装arm交叉编译工具链 sudo apt install gcc-arm-linux-gnueabihf安装之后,在终端输入ar…...
Cocos Creater打包安卓App添加隐私弹窗详细步骤+常见问题处理
最终演示效果,包含所有代码内容 + 常见错误问题处理 点击服务协议、隐私政策,跳转到相关网页, 点击同意进入游戏,不同意关闭应用 一,添加Activity,命名为MyLaunchActivity 二,编写MyLaunchActivity.java的内容 package com.cocos.game.launch;import android.os.Bund…...
UI文件上传
1、文件上传:文件上传是自动化中比较麻烦棘手的部分。 有些场景我们需要上传本地文件到项目里。这种比较麻烦,因为需要点开文件上传的窗口后,打开的是windows的文件选择窗口, 而selenium是无法操作这个窗口的。 selenium只能操作…...
2.凸包优化求解
1.减而治之(Decrease and Conquer) 插入排序 典型的减而治之算法就是插入排序方法 插入排序法: 在未排序中选择一个元素,插入到已经排序号的序列中 将凸包也采用减而治之的方法 2.In-Convex-Polygon Test 怎么判断引入的极点存在于多边形里面还是外面࿱…...
从0开发一个unibest+vue3项目,使用vscode编辑器开发,总结vue2升vue3项目开始,小白前期遇到的问题
开头运行可看官网 链接: unibest官网 一:vscode中vue3代码显示报错标红波浪线 去查看扩展商店发现一些插件都弃用了,例如h5的插件以及vue老插件 解决办法:下载Vue - Official插件(注意:横杠两边是要加空格的ÿ…...
jmeter中文乱码问题解决
修改jmeter.properties配置文件 进入JMeter安装目录的bin文件夹,找到jmeter.properties文件。搜索参数sampleresult.default.encodingUTF-8,取消注释(删除行首的#),并将其值改为UTF-8。保存文件并重启JMeter生效…...
额外篇 非递归之美:归并排序与快速排序的创新实现
个人主页:strive-debug 快速排序非递归版本 非递归版本的快速排序是为了解决在空间不够的情况下,利用栈来模拟递归的过程。 递归版本的快速排序是空间换时间,好实现。 实现思路: 1. 创建一个栈,将数组的右边界下标和…...
[文献阅读] EnCodec - High Fidelity Neural Audio Compression
[文献信息]:[2210.13438] High Fidelity Neural Audio Compression facebook团队提出的一个用于高质量音频高效压缩的模型,称为EnCodec。Encodec是VALL-E的重要前置工作,正是Encodec的压缩量化使得VALL-E能够出现,把语音领域带向大…...
JavaSpring 中使用 Redis
创建项目 配置 Redis 服务地址 创建 Controller 类 由于当前只是些简单的测试代码,所以就不进行分层了,只创建一个 Controller 来实现 jedis 通过 jedis 对象里的各种方法来操作 Redis 此处通过 StringRedisTemplate 来操作 Redis 最原始提供的类是 Re…...
B端可视化像企业数据的透视镜,看清关键信息
在数字化时代,数据已成为企业最宝贵的资产之一。然而,数据的价值不仅取决于其数量,更在于企业能否快速、准确地提取关键信息并据此做出决策。B端可视化技术的出现,为企业提供了一种强大的工具,它如同企业的“透视镜”&…...
【愚公系列】《Python网络爬虫从入门到精通》055-Scrapy_Redis分布式爬虫(安装Redis数据库)
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! …...
【MySQL】SQL语句在MySQL中的执行过程?主要存储引擎区别?
MySQL SQL语句执行过程详解 作为面试官,我来详细剖析一条SQL语句在MySQL中的完整执行过程,这是每个后端开发者都应该掌握的核心知识。 一、连接阶段 建立连接 客户端通过TCP/IP协议与MySQL服务器建立连接(默认3306端口)服务器验证用户名、密码和权限…...