【Java】深入理解Java虚拟机 | 垃圾收集器GC

news2024/12/22 18:26:50

《深入理解Java虚拟机》的阅读笔记——第三章 垃圾收集器与内存分配策略。

参考了JavaGuide网站的相关内容:https://javaguide.cn/

Q:哪些内存需要回收?什么时候回收?如何回收?

2 对象已死吗?

2.1 引用计数法

  • 给对象中添加一个引用计数器
  • 每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;
  • 任何时刻计数器为0的对象就是不可能再被使用的

缺点:很难解决对象之间相互循环引用的问题。

public class ReferenceCountingGC {
    public Object instance = null;
    public static void testGC() {
            ReferenceCountingGC objA = new ReferenceCountingGC();
            ReferenceCountingGC objB = new ReferenceCountingGC();
            objA.instance = objB;
            objB.instance = objA;
            objA = null;
            objB = null;
            //假设在这行发生GC,objA和objB是否能被回收?
            System.gc();
    }
}

如果使用的是引用计数法,objA和objB不会被回收。但实际上运行时,虚拟机并没有因为这两个对象相互引用就不回收它们,也侧面证明了java虚拟机没有采用这种方法。

2.2 可达性分析算法

通过一系列的称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain)。

当一个对象到GC Roots没有任何引用链相连(从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

2.3 引用

在JDK 1.2以前,Java中的引用的定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。

在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用4种,这4种引用强度依次逐渐减弱。

类比于整理桌子的话,强引用是宿舍桌子上的电脑,不得不使用它;软引用是switch,可以放着,但是桌子空间不够的时候收起来也行;软引用是吃完了的薯片袋子,垃圾收集的时候就被处理掉;虚引用是墙上照片里的物品,照片如何并不会对清理桌子的行为产生影响。

强引用

在程序代码之中普遍存在的,类似Object obj = new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。【是程序正常执行的生活必需品,即使内存溢出也不会回收】

软引用

有用但不是必须的对象,在程序将要发生内存溢出异常之前,将把这些对象列进回收范围进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。

弱引用

非必需对象,只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。

2.4 生存还是死亡

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于**“缓刑”**阶段。

要真正宣告一个对象死亡,至少要经历两次标记过程:

  1. 如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法

    • 没有必要,直接死刑哩:当对象没有overwrite finalize()方法,或者finalize()方法已经被虚拟机调用过
  2. finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记

    • 如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列之中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它。
    • 虚拟机触发这个方法,但并不承诺会等待它运行结束。(避免发生死循环等情况,导致整个内存回收系统崩溃)
    • 如果对象要在finalize()中成功拯救自己:只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合。

在下面的代码中,任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,因此第二段代码的自救行动失败了。

建议尽量避免使用finalize(),因为它不是C/C++中的析构函数,而是Java刚诞生时为了使C/C++程序员更容易接受它所做出的一个妥协。它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序。

玛尼玛尼哄~忘掉它吧!

/**
 * 此代码演示了两点:
 * 1.对象可以在被GC时自我拯救。
 * 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
 */
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;
    
    public void isAlive() {
            System.out.println("yes, i am still alive :)");
    }
    
    @Override
    protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize mehtod executed!");
            FinalizeEscapeGC.SAVE_HOOK = this;
    }
    
    public static void main(String[] args) throws Throwable {
            SAVE_HOOK = new FinalizeEscapeGC();
            //对象第一次成功拯救自己
            SAVE_HOOK = null;
            System.gc();
            //因为finalize方法优先级很低,所以暂停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();
            //因为finalize方法优先级很低,所以暂停0.5秒以等待它
            Thread.sleep(500);
            if (SAVE_HOOK != null) {
                    SAVE_HOOK.isAlive();
            } else {
                    System.out.println("no, i am dead :(");
            }
    }
}
finalize mehtod executed!
yes, i am still alive :)
no, i am dead :(

3 垃圾收集算法

3.1 标记-清除算法

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

不足:

  • 效率问题,标记和清除两个过程的效率都不高。
  • 空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

3.2 复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。

现在的商业虚拟机都采用这种算法来回收新生代

IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照 1 : 1 1:1 1:1的比例来划分内存空间。

将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。

HotSpot虚拟机默认Eden和Survivor的大小比例是 8 ∶ 1 8∶1 8∶1,也就是每次新生代中可用内存空间为整个新生代容量的 90 % ( 80 % + 10 % ) 90\% (80\%+10\%) 90%(80%+10%),只有10%的内存会被“浪费”。

当然,我们没有办法保证每次回收都只有不多于10%的对象存活,如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象时,这些对象将直接通过分配担保机制进入老年代。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W08WET3P-1685715965451)(【Java】深入理解Java虚拟机-垃圾收集器GC/image-20230602153512487.png)]

3.3 标记整理算法

复制-收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。

更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。

“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

3.4 分代收集算法

根据对象存活周期的不同将内存划分为几块。

一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

  • 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
  • 而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者**“标记—整理”**算法来进行回收。

6 内存分配与回收策略

6.1 对象优先在Eden分配

  • 大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
  • 如果GC 期间虚拟机又发现 Eden上的对象无法存入 Survivor 空间,将通过 分配担保机制 把新生代的对象提前转移到老年代中去。
  • 执行 Minor GC 后,后面分配的对象如果能够存在 Eden 区的话,还是会在 Eden 区分配内存。
public class GCTest {
	public static void main(String[] args) {
		byte[] allocation1, allocation2,allocation3,allocation4,allocation5;
		allocation1 = new byte[32000*1024];
		allocation2 = new byte[1000*1024];
		allocation3 = new byte[1000*1024];
		allocation4 = new byte[1000*1024];
		allocation5 = new byte[1000*1024];
	}
}

allocation1,分配在Eden上:

img

allocation2,Eden区没有足够空间进行分配,虚拟机发起一次Minor GC。allocation1过大,无法存入 Survivor 空间,将通过 分配担保机制 将其提前转移到老年代中去,老年代上的空间足够存放 allocation1,所以不会出现 Full GC。

img

allocation3allocation4allocation5被分配在Eden上。

6.2 大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的Java对象,比如很长的字符串以及数组

比遇到一个大对象更加坏的消息就是遇到一群“朝生夕灭”的“短命大对象”,写程序的时候应当避免。

6.3 长期存活的对象将进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。

  • 虚拟机给每个对象定义了一个对象年龄(Age)计数器。
  • 如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。
  • 对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度,就将会被晋升到老年代中。

6.4 总结★

GC两大分类

  1. 部分收集 (Partial GC)

    • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;

    • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代整堆收集;

    • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。(只有G1收集器可以做到)

  2. 整堆收集 (Full GC):收集整个 Java 堆和方法区。

各种GC的触发条件

以Serial GC为例:

  1. 当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
  2. 在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC,收集整个GC堆(除了CMS收集器外,其他能收集老年代的GC都会同时收集整个GC堆)。
  3. 如果有Perm Gen的话,要在Perm Gen分配空间但已经没有足够空间时,也要触发一次full GC。
  4. System.gc()也默认触发full GC。

5 垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。虚拟机所处的区域,则表示它是属于新生代收集器还是老年代收集器。

并不存在放之四海皆准、任何场景下都适用的完美收集器。需要根据场景合适选择。

5.1 Serial收集器

最基本、发展历史最悠久的收集器。

一个单线程的收集器,在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。新生代采用标记-复制算法,老年代采用标记-整理算法

Serial 收集器

缺点:Stop The World 会带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短。

优点:它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。

它依然是虚拟机运行在Client模式下的默认新生代收集器。

5.2 ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本。

ParNew收集器除了多线程收集之外,其他与Serial收集器相比并没有太多创新之处,但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。

  • CMS:并发的收集器,作为老年代的收集器使用。
  • CMS无法与JDK 1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作,所以在JDK 1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中的一个。

ParNew 收集器

● 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

● 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

5.3 Parallerl Scavenge收集器

新生代采用标记-复制算法,老年代采用标记-整理算法。

Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。

CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。
吞吐量 = 运行用户代码的时间 C P U 总消耗时间的比值 吞吐量=\frac{运行用户代码的时间}{CPU 总消耗时间的比值} 吞吐量=CPU总消耗时间的比值运行用户代码的时间
Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。

Parallel Old收集器运行示意图

5.4 Serial Old 收集器

Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。

5.5 Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

5.6 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。

整个垃圾回收过程分为4个步骤:

  • 初始标记:暂停所有的其他线程,标记一下GC Roots能直接关联到的对象,速度很快;
  • 并发标记:同时开启 GC 和用户线程,进行GC Roots Tracing,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  • 重新标记:暂停所有的其他线程修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,比初始标记时间稍长,比并发标记时间短。
  • 并发清除:开启用户线程,同时 GC 线程开始对未标记的区域做清扫。

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

CMS 收集器

主要优点:并发收集、低停顿。

缺点

  • CMS收集器对CPU资源非常敏感;
  • 无法处理浮动垃圾;
  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生

由于CMS并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃圾”

G1 收集器

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。

在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。

空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于**“标记-复制”**算法实现的。

可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。

G1 收集器

总结

Q:哪些内存需要回收?什么时候回收?如何回收?

我的回答:

通过GC Roots可达性分析,执行finalize方法后未自救成功的对象需要被回收,(若无finalize方法或者执行过一次,直接死刑),GC Roots包括栈帧中的本地变量表、类中的静态属性、常量引用的对象、Native方法引用的对象。引用可分为强引用、软引用、弱引用、虚引用,强引用的对象不会被回收;软引用的对象在第一次GC后若仍空间不足,再次GC时会被回收;弱引用的对象在第一次GC时就将被回收;虚引用不影响垃圾回收,

什么时候回收?以Serial垃圾收集器为例,对象优先被分配在新生代Eden区(大对象则进入老年代),当Eden区没有空间时,检查老年代空间是否大于历次平均晋升大小,如果大于,进行Minor GC,否则进行Full GC。

回收方法包括:标记-清除法,标记-复制法,标记-整理法。标记-复制法通常用于较少对象存活的新生代,标记-整理法通常用于老年代。不同垃圾收集器的回收方法有所区别。

  1. Serial为单线程垃圾回收器,用于新生代,采用标记-复制法,Serial Old用于老年代,采用标记-整理法;
  2. ParNew收集器是Serial的多线程版本;
  3. Parallel Scavenge优先考虑吞吐量而不是最短等待时间,Parallel Old是其老年代版本。同样使用标记-复制法和标记-整理法。
  4. CMS使用初次标记、并发标记、重新标记、并发清除方法,以获取最短回收停顿时间为目标。
  5. G1收集器将整个GC堆划分为多个Region,保留了分代的概念。以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。

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

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

相关文章

4种普遍的机器学习分类算法

朴素贝叶斯分类 朴素贝叶斯分类是基于贝叶斯定理与特征条件独立假设的分类方法,发源于古典数学理论,拥有稳定的数学基础和分类效率。它是一种十分简单的分类算法,当然简单并不一定不好用。通过对给出的待分类项求解各项类别的出现概率大小&a…

Linux驱动开发(使用I2C总线设备驱动模型编写AT24C02驱动程序)

文章目录 前言一、I2C总线设备驱动模型二、设备树编写三、驱动程序编写1.提供i2c_driver结构体变量并且注册2.注册file_operations结构体3.操作AT24C02 四、应用程序编写五、上机测试总结 前言 本篇文章将讲解如何使用I2C总线设备驱动模型编写AT24C02驱动程序。 一、I2C总线设…

Linux platform 设备驱动实验

目录 1. platform平台简介 1.1 platform总线 1.2 platform 驱动 1.3 platform设备 2.platform平台总线初始化 3. platform驱动框架 4.实验 4.1 无设备树的platform设备注册 4.2 无设备树的platform驱动 4.3 有设备树的platform驱动 1. platform平台简介 当我们向系统…

java设计模式(十二)代理模式

目录 定义模式结构角色职责代码实现静态代理动态代理jdk动态代理cglib代理 适用场景优缺点 定义 代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。说简单点,代理模式就是设置一个中间代理来控制访问原目标对象,以达到…

高度平衡二叉搜索树(AVLTree)(插入与旋转)

目录 简介 AVL的结点类 平衡因子的性质 AVL树的插入 更新平衡因子的接口(ChangeBf) 第一种情况:插入后父节点的平衡因子为0 第二种情况:更新后父节点的平衡因子的绝对值为1 第三种情况:更新后父节点的平衡因子的绝对值为2 旋转接口(…

cam_lidar_calibration代码详解(一)采样优化部分

目录 一、launch启动程序 1.1 run_optimiser.launch标定优化程序 1.2 assess_results.launch重投影误差评估程序 二、主要代码 2.1 feature_extraction_node.cpp文件 2.2 feature_extractor.cpp文件 2.2.1 FeatureExtractor::callback_camerainfo函数 2.2.2 serviceCB函…

QT快速操作Excel的实现介绍及操作类封装

QT中操作Excel还是比较简单的,Qt提供了QAxObject,包装COM组件的类,通过COM通过COM操作使用QAxObject类,使用此类,需要在pro文件中添加"QT   axcontainer "。 基本流程介绍 QAxObject: QAxObj…

SAP从入门到放弃系列之pMRP

最近学习pMRP,查了很多博客,机翻一篇内容非常详细的文章,感谢大佬: 原文地址: pMRP–Predictive Material and Resource Planning in SAP S/4HANA : Step by Step execution pMRP https://blogs.sap.com/2020/04/14/p…

机器学习【线性回归】

机器学习【线性回归】 回归预测的结果是离散型变量,身高和年龄 损失函数:SSE(误差平方和),RSS(残差平方和),误差越大越差 最小二乘法:通过最小化真实值和预测值之间的…

大数据:sparkSQL,历史,DataSet,DataFrame,sparkSession

大数据:sparkSQL 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去找开发,测开 测开的话,你就得学数据库,sql,oracle,尤其sql要学…

第十章:创建和管理表

第十章:创建和管理表 10.1:基础知识 一条数据存储的过程 ​ 存储数据是处理数据的第一步。只有正确地把数据存储起来,我们才能进行有效的处理和分析。否则,只能是一团乱麻,无从下手。 ​ 在MySQL中,一个完…

使用模板方法模式封装协议消息

目录 整体框架 模板方法介绍 关于本案例设计 c impl惯用法 cimpl惯用法好处 此案例impl惯用法的设计 关于序列化和反序列化 序列化和反序列化 本项目使用介绍 谷歌测试 谷歌测试环境 谷歌测试用例 完整源码地址 概述 本文介绍了从 设计模式之模板方法模式协议消…

面对CPU狂飙时的5步解决方案

现在企业对后端开发的要求越来越高,不仅要求我们会写代码,还要我们能够进行部署和运维! 项目上线并运行一段时间后,可能会发现部署所在的Linux服务器CPU占用过高,该如何排查解决? 本文用5步带你搞定线上CPU…

操作系统-进程和线程-同步、互斥、死锁

目录 一、同步互斥 二、互斥的实现方法 2.1软件实现 2.1.1单标志法 2.1.2双标志先检查 2.1.3双标志后检查 2.1.4Petersons算法 2.2硬件实现 2.2.1 TestAndSet指令 2.2.2 Swap指令 三、信号量机制 3.1整形变量 3.2 记录型变量 3.3用信号量实现进程互斥、同步、前驱关系…

Sui与F1甲骨文红牛车队达成合作

在近期达成的一项为期多年的合作协议中,甲骨文红牛车队将利用Sui网络开发,为粉丝带来全新的数字化体验。 甲骨文红牛车队的粉丝将很快在Sui网络上体验到他们最爱的一级方程式车队带来的激情。最近几个赛季一直统治着F1赛场的甲骨文红牛车队,与…

代码随想录算法训练营第五十三天 | 力扣 1143.最长公共子序列, 1035.不相交的线, 53. 最大子序和

1143.最长公共子序列 题目 1143. 最长公共子序列 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符…

2022年值得关注的7个主要SD-WAN趋势

随着SD-WAN技术在2022年继续发展成熟,该技术在集成远程访问、自动化和多云连接方面的支持有望得到更多的改进。 软件定义WAN仍然是增强用户体验(UX),提高安全性,以及提供与基于云计算的应用程序的连接的一项关键技术。 随着SD-WAN的成熟&…

java设计模式之:原型模式

在我们的生活中,有很多例子,都是用到了原型模式,例如:我们去配钥匙的时候,肯定先要有一个原配钥匙才可以去配钥匙;《西游记》中孙悟空可以用猴毛根据自己的形象,复制(又称“克隆”或…

时间序列预测 | Matlab基于北方苍鹰算法优化随机森林(NGO-RF)与随机森林(RF)的时间序列预测对比

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 时间序列预测 | Matlab基于北方苍鹰算法优化随机森林(NGO-RF)与随机森林(RF)的时间序列预测对比 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %-----------…

【Apache-Flink零基础入门】「入门到精通系列」手把手+零基础带你玩转大数据流式处理引擎Flink(基础概念解析)

手把手零基础带你玩转大数据流式处理引擎Flink 前言介绍Apache Flink 的定义、架构及原理Flink应用服务Streams有限数据流和无限数据流的区别 StateTimeAPI Flink架构体系 Flink操作处理Flink 的应用场景Flink 的应用场景:Data Pipeline实时数仓搜索引擎推荐 Flink …