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

news/2024/5/20 12:31:42/文章来源:https://blog.csdn.net/qq_43787197/article/details/131015127

《深入理解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 :)");}@Overrideprotected 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.luyixian.cn/news_show_690754.aspx

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

相关文章

剪映自动打关键帧

牙叔教程 简单易懂 这是给单张图片打关键帧的教程, 给图片打关键帧有四个步骤 鼠标点选图片打起始帧跳转到图片末尾打结束帧 打帧是一件很费手的事情, 所以我写了个自动化的代码, 专门用来打关键帧, 使用的软件是 AutoHotkey 关键帧参数的详细解释 剪映 自动打关键帧 AutoH…

Azure Log Analytics:与Power BI集成

注:本文最初发布于https://d-bi.gitee.io, 2023年6月迁移至CSDN 前述 Azure Log Analytics是Azure Monitor中的一项分析服务。本文将讲述通过Log Analytics与Power BI集成的方式,获取Power BI工作区内的日志信息,包括各PBI数据集的CPU消耗&a…

Web安全总结

目录 网站架构一般web服务器结构相比于传统的网络攻击,基于web的攻击有什么不同?HTTP协议HTTP响应拆分攻击HTTPS针对HTTPS协议的攻击那么如何保证证书的唯一性? HTTP会话Cookie和Session的关系HTTP会话攻击解决方案 Web访问中的隐私问题Web应…

chatgpt赋能python:Python如何空一行:介绍

Python如何空一行:介绍 在Python编程中,经常需要在输出文字或代码时进行空行分隔。一个常用的场景就是在代码中加入注释,将注释与代码分开,使代码逻辑更加清晰易懂。在某些情况下,也需要在输出文字时进行空行分割&…

ChatGPT-Plugins-Searchable

ChatGPT Plus 用户应该都知道Plus已经开放了插件功能,但是在插件商店里存在一个较大的问题插件数量超过100款,却没有便捷的搜索功能。 而我们在查找一款插件时,需要从插件商店的第一页点击到最后一页一个个找,显然这非常的麻烦。 …

JUC基础-0606

9.ReentrantReadWriteLock读写锁 9.1 锁的基本概念 悲观锁:不支持并发,效率低,但是可以解决所有并发安全问题 乐观锁:支持并发读,维护一个版本号,写的时候比较版本号进行控制,先提交的版本号…

【Vue】三:Vue组件: 组件使用和组件嵌套

文章目录 1.第一个组件1.1不使用组件前1.2创建组件1.3注册组件1.4使用组件1.5 细节 2.组件嵌套 1.第一个组件 1.1不使用组件前 1.2创建组件 Vue.extends({该配置项和new Vue的配置项几乎相同})区别: (1)创建Vue组件的时候,不能使…

Kubernetes之pod

Kubernetes之pod 在通过docker运行程序时,我们通常会制作Dockerfile文件构建镜像。也可以基于某个镜像运行容器在容器中安装组件之后,再基于容器生成镜像 使用如下命令可生成镜像,想了解更多参数请添加–help docker build -f Dockerfile路…

【Leetcode -138.复制带随机指针的链表 -2130.链表最大孪生和】

Leetcode Leetcode -138.复制带随机指针的链表Leetcode -2130.链表最大孪生和 Leetcode -138.复制带随机指针的链表 题目:给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 构…

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

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

企业应该如何选择适合自己的直播平台?

企业应该如何选择适合自己的直播平台?本文将从功能需求、可靠性与稳定性、用户体验、技术能与售后服务能力等方面进行综合考虑,帮助您做出明智的决策,或是说提供选型方面的参考。 企业在选择一家直播平台时应考虑以下因素: 1. 企…

【链表的分类】

链表是一种常用的数据结构,它由一系列节点组成,每个节点包含一个数据元素和指向下一个节点的指针。根据节点的连接方式和节点的性质,链表可以分为多种类型。 单向链表(Singly Linked List) 单向链表是最基本的链表类…

PyGame游戏编程

Python非常受欢迎的一个原因是它的应用领域非常广泛,其中就包括游戏开发。而是用Python进行游戏开发的首选模块就是PyGame。 1. 初识Pygame PyGame是跨平台Python模块,专为电子游戏设计,包含图像、声音等,创建在SDL(…

sms开发文档

sms系统设计参考毕业设计-----------学生选课管理系统的设计 一、使用axios 来实现网页中ajax请求 首先说到axios,是一个类库,他的底层基于ajax库,通常用于ajax请求 ajax又是什么 ajax是一种创建快速动态网页的技术, 传统的页…

LeetCode 24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 示例 1: 输入:head [1,2,3,4] 输出:[2,1,4…

chatgpt赋能python:Python如何使用空行优化SEO

Python 如何使用空行优化 SEO 在网页排名算法中,空行的使用可以对网页的排名产生影响。在 Python 中,空行的使用也被用来优化代码和提高代码的可读性。本文将介绍如何在 Python 中使用空行来优化代码和优化 SEO。 空行的作用 在 Python 中&#xff0c…

直播问答功能(互动功能接收端JS-SDK)

功能概述 本模块主要用于展示问答模块。 初始化及销毁 在实例化该模块并进行使用之前&#xff0c;需要对SDK进行初始化配置&#xff0c;详细见参考文档。 在线文件引入方式 // script 标签引入&#xff0c;根据版本号引入JS版本。 <script src"https://websdk.vi…

OA系统,企业数字化转型的重要工具,用现成还是自己搭建呢

什么是OA系统 OA系统是办公自动化系统的简称&#xff0c;它是指一种基于计算机技术的办公工作管理系统&#xff0c;用于协调和规划企业内部各部门的信息发布、通信、人员流动、文档管理等方面的工作。它可以有效地提高企业办公效率和工作效益&#xff0c;优化企业内部沟通协作…

Java之旅(五)

运算符 算术运算符 加法&#xff08;&#xff09;减法&#xff08;-&#xff09;乘法&#xff08;*&#xff09;除法&#xff08;/&#xff09;取余&#xff08;%&#xff09;一元运算符 自增运算符&#xff08;&#xff09;自减运算符&#xff08;--&#xff09;变量前就先运…

元宇宙应用领域-教育

教育是一个国家发展的基础&#xff0c;在科技发展的时代&#xff0c;元宇宙将会帮助教育行业实现跨越式发展。 元宇宙与教育的结合将会对传统的教学模式带来翻天覆地的变化。它能将线上教学、线下体验、远程互动等优势集于一身&#xff0c;也能把教师从繁重的重复劳动中解放出…