Java最新技术介绍和分析 (202305)

说明:本文完成了2023年5月份,当时最新的LTS版本是Java17,本文在撰写时参考了美团技术团队和阿里JDK团队相关的文章,以及本文也引了用文章中的图片。在此表示感谢!

Java版本火车

相信老牌的Java开发者和爱好者把Java的版本停留在经典的java5,Java 6,以及Java8和Java11版本上,这几个版本都是java的里程碑版本。 Java 社区决定从Java 9开始每半年发布一个新版本,同时LTS(long-term support)版本从原来的每3年改为每2年发布一个。下图是我根据Oracle官网Support Roadmap做的甘特图。

Java 7,8,11. 17以及还未发布的Java 21均是LTS(Long Term Support)版本,Oracle提供5年的维护周期,以及3年的付费额外支持,一共8年维护周期。

到目前为主,工业界的主力版本依然是Java 8和11. 随着云计算,大数据,以及多核的快速发展,Java社区也在适就这些变化,在随后的Java版本中增加了很多对云场景、新硬件的技术,比如对容器技术的技术,对大内存和NUMA技术的支持。

当前团队从2021年开始就考虑使用ZGC新特性,充分发挥大内存的特点,能更好满足大数据对多核和大内存的发展趋势,目前我们正在考虑使用Java 17 LTS版本来支持大数据领域的技术发展。​

Java重要特性介绍

当前团队当前主流的Java版本是Java8和11,为了让大家对Java高版本特性有更好的了解,我从Java12开始直到Java17最为重要的特性做了初步的分析。由于篇幅有有限和对Java了解深度不足,本文只介绍Java虚拟机领域较新的特性,不涉及Java语言和Java框架发展分析。Java技术的爱好者均可从这里找到完整的Java提案。

因此,本文重点分析Java12到Java17在性能方面和云计算方面取得的进展。

性能​

ZGC

ZGC无疑是Java GC算法的一个大变革,相比之前的GC算法,提供了以下亮眼的特性:

  • 亚毫秒级的最大暂停时间 (最大暂停时间为10ms,但实际暂停时间基本在1ms以内)
  • 暂停时间与堆大小无关,与活跃对象和根引用集合大小无关
  • 堆大小从8M到16T都可以支持

ZGC最早出现在JDK 11中作为实验特性,从Java15开始作为生产特性使用. ZGC为什么能提供这么亮眼的特性,它的关键设计要点有如下:

  • 最大限度的Concurrent: 除了几个非常短和工作量较为固定的阶段需要STW(Stop The World)外,其它阶都是GC线程和业务线程并发执行
  • Region-Based: 可以灵活支持不同大小的Heap
  • Compacting: 使用Compact机制,同时也不再分年轻代和老年代
  • NUMA-aware: 感知机器的NUMA拓扑结构,让线程分配内存让尽量在本地Node上分配内存,让后续内存访延迟更小
  • 使用3颜色指针和读屏碍:这是ZGC一种创新,通过通过读屏障,在Compact阶段,用户线程无须停下来,而是帮着GC线程在访问对象的同时顺便把Compact的工作也做了;3颜色则表示对象指针的3种视图来表示对象是不是正在GC状态,无须在Java对象上标准Mark状态。
  • 使用透明大页(Transparent Huge Page, THP):THP是比Linux默认的hugetlb更灵活,ZGC天生支持THP,让堆对象的访问性能更高,THP(2M页)与4K页内存访问相比,性能会有5~8%的性能提升

ZGC一个回收集周期图示如下:

从上图可以看出,只有Initial Mark,Remark和Initial Transfer过程才需要STW,其它阶段都是GC线程与业务线得程并发执行,不会造成业务线程停顿。上述的3个会产生STW阶段的工作量只与根引用集合大小相关,实际上这几个过程都只需要做一次根对象扫描,耗时非常短,通常在1ms以内,也这是为什么ZGC号称最大停顿时间时只有亚毫秒级。

3颜色指针和读屏障

​为了更方便支持在Compact阶段与业务线程并发执行,ZGC提出了3颜色指针方案:方案原理非常简单,那就是将操作系统分配的一块物理内存,同时映射到3块不同的虚拟地址空间,并且同一个对象的虚拟地址在这3个空间里,只是地址的高位是不同的,低42位是相同。这样就可以通过修改对象引用地址的高位,实现不同的地址视图切换。

上图是3个地址空间的划分,M0,M1和Remapped,中间有一个预留区域,是为了与下图的指针位相匹配的。

Marked 0, Marked 1和Remapped这3个标志位是互斥的,任何时候只能置一个位。当Marked 0置位时,指针指向M0空间,当前Marked 1置位时,指针向批M1空间,当Remapped置位时,指针指向Remapped空间。​

ZGC的几个关键流程如下:

  • 初始化:GC线程扫描所有根引用对象,并将这些引用视图修改为Remapped
  • 并发标记阶段:无论是并行的GC线程扫描到对象时,将对象视图切换成M0,业务线程在些阶段访问到对象时,读屏障代码会将对象视图修改为M0. 这个阶段结束后,对象的地址视图是M0时,表示是一个活跃对象。对象依然停留在Remapped视图时,表示它是非活跃对象,可以回收。
  • 并发转移阶段(compact):这个阶段的功能,是将活跃对象搬依次搬到Region的低地址区。过程是GC线程和业务并发执行,GC线程专注于搬移对象,但此过程里恰巧业务访问对象时,会在读屏障代码里将对象实施转移。对象是否已经转换成功了,全靠地址视图来判定。如果为M0时,说明还没有转移,则转移并将地址视图切换成Remapeed,否则不用做处理(因为有另一个线程帮忙做了)。

以下是JDK社区官方测试结果:

左图是Java标准的的性能测试套结果,数据做了归一处理,将ZGC的max-jOPS归一化成100%。max-jOPS表示吞吐性能,数值越大越好,critical-jOPS反应延迟性能,数据越大越好。从测试效果来看,ZGC比G1有明显的提升,max-jOPS提升10%,critical-jOPS提升约40%。

右图同时是SPECjbb@2015测试时,跟踪到的GC暂停时间。从测试结果来看,ZGC的平均值,99.99分位值和 Max值都在1ms左右,吊打此前任何版本的GC算法。

Virtual Thread

提到Java已发布的Virtual Thread,必须要提起它的真身——协程,而谈到协程,又必须多嘴提一下没有协程前的血泪史。

在刚开始的互联网时代,Appach HTTP server作为web server的实现者,以process per client的方式支持多client并发访问,在并发量达到10K时(可详见C10K问题),操作系统完全无法承受大量进程所占用的资源开销。接着Unix/Linux出现了kqueue/select多路侦听技术,client的消息调度与处理不再交给process或thread调度了,而是每个应用自己来调度client请求。通过类似于Java NIO或者事件回调这种异步处理技术,可以实现一个server高并发,性能表现非常优异,典型的例子就是Nginx。随着异步回调技术的发展,开发人员很快就陷入了回调地狱的陷阱。一个连贯的线性处理逻辑,必须拆分成多个小片段,散落在多个回调事件处理钩子中,可维护性差。在Java领域,使用reactive programming是一个方向,典型的是Vert.X框架帮助开发人员编写反应式程序。

随后协程的出现,能完美解决这个问题。在代码编写上(形式上):client处理代码只需写一个类似于while(1)的函数,读取request,然后process,再发送reponse给client,依次循环直接client主动关闭链接。在实际执行上,server由于大量的client链接,也有有大量的协程存在,但当一个client在recv 等待request时,协程调度器会快速切换另一协程(client)进行处理。 相对于传统的由kernel对线程进行调度,转换成由协程调度器在线程上下文内对协程进行调度,效率非常。

Java的Loom项目就是推动Java协程落地的项目,下图是从编程的复杂性和运行时的可伸缩性,分别几个技术方向的特点,协程是兼顾了编程简单化和可伸缩性两个方面,鱼和熊掌可兼得。

Java19开始提供Virtual Thread技术,作为一个preview特性,这意味Virtual Thread技术并没有定稿,它的实现和API在将来的生产版本也许会改变。

在介绍Virtual Thread编程API前,先说一下相关的技术概念 :

​​

如上图所示,相比传统的Java线程模型来说,增加了Virtual Thread和Platform Thread。 Virtual Thread比较容理解,就是程序代码中的一个Virtual Thread,但是Platform thread是什么呢?这个并不难,它是协程调度器,同时也是协程的执行载体。Virtual thread和Platform Thread关系并不难理解,Plaform Thread自己会执行 Virtual Thread代码,并且它将多个VT代码交织到一起串着执行。这就像操作系统上的线程与硬件CPU的关系,CPU执行线程,并且是将线程代码交织到一起串着执行。

Plaform thread在任一时刻,只能执行一个Virtutal Thread,其它还没有得到执行的Virtual Thread 称为Unmounted Ready Virtual Thread(未绑定的就绪虚执线程),另外还有一些在等待网络报文请求的Virtual Thread称为Unmounted Blocked Virtual Thread(未绑定的阻塞虚拟线程)。

当Virtual Thread执行到会阻塞的函数调用时(比如InputStream中的read方法),Platform会注监控到,然后把该Virtual Thread注册到一个类似于select/epoll的事件侦听器上(了解NIO的朋友应该不陌生),接下来是Platform Thread 会执行相关的功能,并把当前Vritual Thread放到 Blocked列表上,然后从Ready的Virtual Threads上挑一个来执行。当网络报文达到时, 操作通知到Plaform Thread,然后Platform Thread把相应的Virtual Thread唤醒,放到Ready Virtual Thread,等待下一次被选中执行。

下面简要介绍Virtual Thread的API:

创建Virtual Thread 可用 Thread.ofVirtual()方法:

Thread thread = Thread.ofVirtual().name("duke").unstarted(runnable);​

或者使用Thread.Builder来生成一个ThreadFactory,然后可以批量产生多个相同属性的Virtual Thread。

请注意,Virtual Thread在对象上没有创建新的子类,而且用java.lang.thread来表示传统线程或者Virtual Thread,可通过Thread.isVirtual()方法来测试是否为Virtual Thread。

由于Virtual Thread在调用阻塞方法时,都需要Platform Thread进行监控和捕捉,所有涉及IO操作的API都需要进行修改,以支持Virtual Thread功能。因此涉及到了大量的API修改,列表如下:

  • java.util.concurrent
  • Networking
  • http://java.io
  • Java Native Interface (JNI)
  • Debugging (JVM TI, JDWP, and JDI)
  • JDK Flight Recorder (JFR)
  • Java Management Extensions (JMX)
  • java.lang.ThreadGroup

然而Virtual Thread(和协程一样)并不是万能的,它的应用场景是高并发大量网络请求,通过Platform thread快速切换Virtual Thread获得高效的性能,同时提供线性逻辑的编程方式。但对对计算型任务没有任何效果,计算型任务通常是根据CPU个数来划分并行度,中间不涉及大量的线程调度。

Virtual Thread特性到目前为止,还没有进入商用阶段。但在回过头来看看Java发展最为红火时代出现的编程框架,无一例外都使用线程池来解决多大并发问题。这些多线程架框架在持续优化,为开发人员屏蔽了很多技术细节和复杂性。所以Virtual Thread的出现,能否给Java社区带来翻天覆地的变化,我们拭目以待。

Vector API

向量(Vector)这个词相信对大家来说并不陌生,传统的CPU属于SISD(单指令单数据),随着图形计算的需求,SIMD(单指令多数据)功能应运而生。Intel处理也经历了从MMX到SSE,再到现在的AVX512指令。从软件层面说来,很多底层运行库都开始使用SIMD指令来优化内存处理,比如典型的例子是glibc的memcpy函数就使用了SSE指令加速内存拷贝速度,另一个例子应该gcc编译器在-O3优化层级下,对C/C++ for循环也尽力做向量化处理(即用SSE指令进行优化)。

用下面两例代码来体会一下Java编程世界里SISD和SIMD的差异:

// SISD 例子
void scalarComputation(float[] a, float[] b, float[] c) {for (int i = 0; i < a.length; i++) {c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;}
}// SIMD 例子
void vectorComputation(float[] a, float[] b, float[] c) {int i = 0;    int upperBound = SPECIES.loopBound(a.length);for (; i < upperBound; i += SPECIES.length()) {// Boxing: from array to SIMD Vectorvar va = FloatVector.fromArray(SPECIES, a, i);var vb = FloatVector.fromArray(SPECIES, b, i);// parallel operation for multiple datavar vc = va.mul(va).add(vb.mul(vb)).neg();// Unboxing: from SIMD Vector to arrayvc.intoArray(c, i);}
}

SISD代码,只能依次对每份数据进行运算,而SIMD代码可以每次同时对16份数据(512 / 32 = 16)同时进行运算,是因为AVX512能同时处理位宽为512bit的多份数据。下图可以形象说明SISD和SIMD处理逻辑上的差异。

看似完美的SIMD肯定能带来性线的加速比,但事实并非如此,我们先看一下测试结果

从测试数据来看,效果并不理想。上图是一个基础的向量相加运算,并没有带来任何性能提升;下图是向量FMA (Fused-Multiply-Add)运算,在内存大小不超过L2时获取10+倍的性能加速,但数据量再大起来后,加速比回到2-3倍左右。

在这里我们可以有一些猜测,由于SIMD硬件宽度有限(以AVX512为例),每512bit数据要进行一次Boxing和Unboxing操作(FloatVector.fromArray和FloatVector.toArray),SIMD获得的红利大量分都这些操作的磨损了。

重新审视最新的 JEP (https://openjdk.org/jeps/438)会发现这个问题已在Java意料之中,Vector API有两个实现,其一是上面测试数据所体现的,使用API库的方式实现。第二个则是使用JVM的intrinsics上,通过C2 JIT编译器直接翻译成Native汇编代码,比如上图的代码在Intel X64机器翻译成如下的汇编代码:

0.43%   / │  0x0000000113d43890: vmovdqu 0x10(%r8,%rbx,4),%ymm07.38%  │ │  0x0000000113d43897: vmovdqu 0x10(%r10,%rbx,4),%ymm18.70%  │ │  0x0000000113d4389e: vmulps %ymm0,%ymm0,%ymm05.60%  │ │  0x0000000113d438a2: vmulps %ymm1,%ymm1,%ymm1
13.16%  │ │  0x0000000113d438a6: vaddps %ymm0,%ymm1,%ymm0
21.86%  │ │  0x0000000113d438aa: vxorps -0x7ad76b2(%rip),%ymm0,%ymm07.66%  │ │  0x0000000113d438b2: vmovdqu %ymm0,0x10(%r9,%rbx,4)
26.20%  │ │  0x0000000113d438b9: add    $0x8,%ebx6.44%  │ │  0x0000000113d438bc: cmp    %r11d,%ebx\ │  0x0000000113d438bf: jl     0x0000000113d43890​

直接把Java语言翻译成Native 代码,少了JDK API中间赚差价。从上面的汇编代码来,完全没有Boxing和Unboxing代码,性能发挥得极致。

从目前看来,向量计算在Java有以下的支持

  • 把向量当作值类型进行处理
  • 支持Vector API通用化,由intrisincs根据硬件特点,翻译相应该的SIMD硬件指令,同时能屏蔽SIMD功能上的差异,无法用硬件实现的退出成非向量指令
  • 应用Intel的SVML数学库,实现SIMD不能提供的计算能力全部转交给SVML来实现。

目前最新的JEP显示,Vector特性依然还是一个实验特性,期待能早日应用到生产中。

Numa感知​

NUMA (Non-uniform memory access) 技术是计算机体系统架构应对多核挑战下的重要优化:为了减少公共内存带宽的压力和瓶颈,它采用了内存分层的访问结构,正如IDC物理网络的Leaf-Spin架构一样。可从下图的物理结构一窥究竟:

上图是典型的Intel处理器当前的配置,分成两个socket,每个Socket有本地的内存,CPU和硬件。这里我们重要关注内存,所有socket内存条是统一编址的,但是CPU访问本地内存时,速度比访问远端socket的内存要快好几倍,因为他们之间需要一个socket间互联通道QPI进行通信。

实际上,硬件和操作系统对NUMA的支持已经是比较友好了,一个进程在申请内存时,操作系统会尽量从本地内存空间中分配内存给程序使用,减少远端内存访问的概率。

但是Java自身托管了Heap内存管理,所以它需要感知 NUMA,才能在适合的位置分配内存,让业务线程访问性能更高。同样由于NUMA对性能的意义重大,Java在ParallelGC是已支持NUMA感知的,但是G1回收算法在Java14之前并没有支持,所以在Java14提出了支持NUMA感知特性。​

G1感知NUMA的原理并不复杂:在Heap初始化时,比较平均地从两个Socket节点申请内存;当业务线程new对象时,优先考虑从线程所在socket的heap空间上分配对象,这样对年轻代对象非常友好。因为年轻代对象生存周期往往很短,所以业务线程访问时往往没有调度到另一个Socket的CPU上,极大概率是本地访问,性能高。但如果本地Heap内存空间不足时,触发GC回收,再分配。但如果万一本地Heap没有空闲内存了,但另一个socket Heap有较多的空闲空间,G1会failback到远端Heap分配对象。

下图是SPECjbb@2015的测试结果,G1支持NUMA感知是在JDK-14 b24合入的,B23是它前一个版本,它们的性能对比最能说明优化效果。该测试使用了512GB的G1 heap做SPECjbb@2015 benchmark测试,从max-JOPS指标来看,提升了20.64%,而critical-JOPS也有9.52%的提升。

同样地,ZGC在设计是就支持NUMA感知,这样不做详细分析和讨论。​

云原生​

GraalVM

在云计算之前, Java有着独特的优势,在企业应用领域大红大紫,开源大数据领域all in Java。但在随着云计算的蓬勃发展,Java的优势反而成了它的不足。 传统的Java程序难以支持毫秒级的启动时间,厚重的应用编程框架,大大逊色于轻量级Javacript来开发FaaS应用,Java实际应过程遇到的各种性能和多语言问题。 为了应对云计算,以及当下发展火热云原生技术, Oracle研发了GraalVM,一种通用JVM解决方案,以解决在云计算场景的痛点。

GraalVM有以下的技术特点:

  • 支持大量的编程语言:包括原本就支持的JVM语言,以及基于LLVM的语言(C/C++),动态语言:Javascript, Ruby, Python, R语言,也支持动态语言引擎:各种Javascript引擎,FastR引擎,RubyTruffle引擎
  • 支持静态编译:把各种语言直接翻译成Native代码,应用程序进行二进制执行,大大加速了应该程的启动时间
  • 高性能:完全Native代码运行,没有翻译解释执行,同时也消除跨语言调用的成本
  • 底噪小,快速启动,多语言联合调用:解决了传统Java应用需要占用大量内存, 以及启动慢问题,对Java应用上云提供便利

下图是GraalVM官网给出的生态系统:

启动时间和内存占用,这两个云计算极为关注的竞争力,它的表现非常优异,如下两图的测试结果可看到。

上两个用Java生态下的3个应用进行测试对比,启动时间快50多倍,内存底躁降低了5倍以上。

在跨语言方面,GraalVM提供了一种在多语言之间无逢传值的方法,而不像传统方式那样需要序列化和反序列化,大大提高的跨语言的性能。正是因为跨语言能力的出现,开发者可以很容易利用另一个语言最新的库进行软件开发,这种便利性大大提升了研发效率。

启动时间是Java整个社区面临的最大挑战之一。下图清楚地展示了为什么Java启动这么慢,JVM有太多的初始化工作需要做。

这个问题最彻底的做法是将Java代码直接翻译成Native,程序可以马上直接运行,不需要加载字节码过程和第一次的解释执行,性能效果最优。由于没有了JVM,JIT组件,可以省去这部分组件所占用的内存开销,也能减少应用image的体积,这完全符合原生的理念和方法。

Java在GraalVM之前并非没有尝试走过这条路,从Java 2之前的GCJ,再到后来的Excelsior JET,再到现在GraalVM提出的通过静态编译解决Java启动慢问题,一路走过,遇到不少挑战。

但这并不是一条容易的技术路线,Java从Bytecode层面来说,它是一个动态语言,它支持灵活的反射技术,支持AOP(Aspect-Oriented Programming)编译技术,意味着JVM给开发者开放了灵活的动态编程技术。因此,在这种便利下,很多信息(比如方法代码,甚至是类)都是无法在编译时确定的,而是在运行时动态地由代码来生成决定,所以无法在在编译阶段直接生成Native。这也就是为什么在此之前只能做到AOT( ahead-of-time)技术。

但如果要求开发者把上述所有态动性功能都去掉,不需要使用,Java生态下的重要组件,如Srping, Hibernate必然跑不起来,整个Java生态会轰然崩塌。于是一条迂回曲折之路必须会摆在各个架构师前面。前面提到GraalVM支持翻译成Native代码是指其它语言跑在GraalVM可以翻译成Native,或者那些没有使用高级动态功能的 Java可以翻译成Native,但对于绝大多数的Java应用来说,还是一条很长远的路。

容器化

云计算和云原生技术栈当中,容器是最重要的技术之一。以Docker为代表的容器技术流派是最通用的,它是Linux操作系统上的Namespace和cgroup两大技术为支柱。所以,Java支持容器技术变更犹为重要。

Kubernetes作为最重的容器调度平台,在上面部署应用时,当然不少了指定每个pod的 Request和Limit资源要求(包含CPU和memory)。但由于Linux下的Namespace是一种弱隔离,Java应用程序运行在Kubernetes上之后,它仍然要读取/proc/meminfo和/proc/cpuinfo来获取它运行环境的资源情况,来决定Java Heap应该开多大,池程池应该开多大。如果一个Java容器只申请了4C8G(4 core and 8 G memory)资源,但跑在一个96C和512G的机器上面。那它会理所当然地认识它有96个Core可运行,接近500G内存可自由使用。但在云原环境上,这样做很容易带来致命的问题,那就是不断地触发OOM,服务难以正常提供。当然开发者可以通过Java环境变量传递各种资源约束,但这种做法难以适合一个应用跑在多种不同的资源环境上。

自Java17开始,OpenJDK支持CgroupV2。JDK会读取它所在的cgroup配置来感知它自己的CPU和memory配置,而不是从原来的/proc/meminfo和/proc/cpuinfo来获取这些信息。 下面是个最简例子,展示了容器里面的java从Cgroup里读到了CPU和memory的配置。

$ java -XshowSettings:system -version
Operating System Metrics:Provider: cgroupv2Effective CPU Count: 2CPU Period: 100000usCPU Quota: 200000usCPU Shares: 1024usList of Processors: N/AList of Effective Processors, 4 total:0 1 2 3List of Memory Nodes: N/AList of Available Memory Nodes, 1 total:0Memory Limit: 1.00GMemory Soft Limit: 800.00MMemory & Swap Limit: 1.00Gopenjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment 21.9 (build 17.0.2+8)
OpenJDK 64-Bit Server VM 21.9 (build 17.0.2+8, mixed mode, sharing 

当然,Java应没有屏去以往的获得资源方式,而且完全兼容,同时也支持通过API获取Cgroup的配置,具体的使用方式请参考相关的技术文档。有了这一能力,Java更贴近云原生,用户体验更好。

小结

本文重点分析Java12到Java17的一些重进展,以及一些现在还没有完全成熟的技术,比如Vector API, GraalVM。从收益来说,免费的性能收益是令人激动的,对云原生的支持也让运维成本大大降小。但除了这两个方面,Java还涉及很多其它的领域,比如编程语言能力的拓展,开发和调试工具的发展,对整个语言生态极其重要。希望有机会再做这些方面的分析和总结。

参考文献

  1. 从 JDK 9 到 19,认识一个新的 Java 形态(内存篇)https://mp.weixin.qq.com/s/cF6JgJIOCF6Jxg520rRbLA
  2. Java 18: Vector API — Do we get free speed-up? https://medium.com/@Styp/java-18-vector-api-do-we-get-free-speed-up-c4510eda50d2
  3. JDK ZGC introduction https://wiki.openjdk.org/display/zgc/Main
  4. 新一代垃圾回收器ZGC的探索与实践 https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html
  5. JEP 426: Vector API (Fourth Incubator) https://openjdk.org/jeps/426
  6. JEP 425: Virtual Threads (Preview) https://openjdk.org/jeps/425
  7. 云原生时代,Java 的危与机 https://www.infoq.cn/article/rqfww2r2zpyqiolc1wbe
  8. Java 17: What’s new in OpenJDK's container awareness https://developers.redhat.com/articles/2022/04/19/java-17-whats-new-openjdks-container-awareness
  9. NUMA-Aware Memory Allocation for G1 GC https://sangheon.github.io/2020/11/03/g1-numa.html

编辑于 2023-12-29 16:22・IP 属地广东

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

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

相关文章

【技巧】IDEA 使用小技巧(三)

IDEA 使用小技巧&#xff08;三&#xff09; 配置目录Ctrl 鼠标方法缩小字体 配置目录 IDEA 在使用的过程中会在 C 盘的用户目录下写入相关配置&#xff0c;目录如下&#xff1a; "C:\Users\个人用户名\AppData\Local\JetBrains" "C:\Users\个人用户名\AppDa…

Halcon顶帽运算与底帽运算的应用

Halcon顶帽运算与底帽运算的应用 文章目录 Halcon顶帽运算与底帽运算的应用1. 提取小的物件2. 校正非均匀光照 正如上文所说的&#xff0c;顶帽运算返回的像素部分是尺寸比结构元素小的&#xff0c;并且比较亮的局部小区域&#xff1b;底帽运算返回的像素部分是尺寸比结构元素小…

打造炫酷粒子效果的前端利器tsParticles

前端潮流速递 &#xff1a;打造炫酷粒子效果的前端利器tsParticles 在现代前端开发中&#xff0c;动画和视觉效果是吸引用户的关键元素之一。而实现炫酷而引人入胜的粒子效果&#xff0c;常常需要耗费大量的时间和精力。然而&#xff0c;有了 tsParticles&#xff0c;这一切变…

网络安全—PGP8.1软件应用

文章目录 安装PGP8了解工作原理 PGP使用准备工作加密与解密加密者视角&#xff08;发送方&#xff09;接收者视角&#xff08;接收方&#xff09; 签名签名方&#xff08;发送方&#xff09;验证签名方&#xff08;接收方&#xff09; 补充加密签名一段文字签名后的格式 验证解…

Java中关键词strictfp有什么作用?

在Java中&#xff0c;关键词strictfp用于声明一个方法、类或接口是严格遵守浮点数计算规范的。 具体作用包括&#xff1a; 保证浮点数计算的结果在不同平台上是一致的&#xff0c;避免由于浮点数计算的不精确性导致的结果不确定性。 指定了严格的浮点数计算规则&#xff0c;禁…

【解决】Unity 设置跨设备分辨率表现

开发平台&#xff1a;Unity 2018版本以上 开发语言&#xff1a;CSharp 编程平台&#xff1a;Visual Studio 2022   问题描述 使用 UnityEngine.dll 中关于设置分辨率的方法时&#xff0c;无法满足应用以设定分辨率进行屏幕显示问题。因而造成画面不同程度的拉伸情况。而这种情…

机器学习-基于Word2vec搜狐新闻文本分类实验

机器学习-基于Word2vec搜狐新闻文本分类实验 实验介绍 Word2vec是一群用来产生词向量的相关模型&#xff0c;由Google公司在2013年开放。Word2vec可以根据给定的语料库&#xff0c;通过优化后的训练模型快速有效地将一个词语表达成向量形式&#xff0c;为自然语言处理领域的应…

C# StringBuilder对比string的优点和15大案例

文章目录 StringBuilder和String 对比1. **循环内字符串连接**2. **构建大型日志消息**3. **格式化长字符串**4. **SQL 查询构造**5. **从文件读取并合并行**6. **拼接数组元素**7. **格式化电子邮件模板**8. **处理用户输入流**9. **JSON 或 XML 格式的序列化与构建**10. **动…

leetcode算法题之递归--二叉树中的深搜总结

递归的本质是找重复的子问题 文章目录 1.计算布尔二叉树的值2.从根节点到叶节点数字之和3.二叉树剪枝4.验证二叉搜索树5.二叉搜索树中第K小的元素6.二叉树的所有路径 1.计算布尔二叉树的值 计算布尔二叉树的值 class Solution { public:bool evaluateTree(TreeNode* root) …

Spring Cloud + Vue前后端分离-第9章 大文件断点续与极速秒传

源代码在GitHub - 629y/course: Spring Cloud Vue前后端分离-在线课程 ​​​​​​Spring Cloud Vue前后端分离-第9章 大文件断点续与极速秒传 作为一个视频网站&#xff0c;一个文件小则几十M&#xff0c;大则上G&#xff0c;上传一个大文件受网络影响很大&#xff0c;文…

【网络技术】【Kali Linux】Wireshark嗅探(四)域名系统(DNS)

一、实验目的 本次实验使用wireshark流量分析工具进行网络嗅探&#xff0c;旨在了解域名系统&#xff08;DNS&#xff09;的工作原理。 二、域名系统概述 简单来说&#xff0c;域名系统&#xff08;Domain Name System, DNS&#xff09;将域名&#xff08;可以理解为“网址”…

Prometheus-AlertManager 邮件告警

环境,软件准备 本次演示环境&#xff0c;我是在虚拟机上安装 Linux 系统来执行操作&#xff0c;以下是安装的软件及版本&#xff1a; System: CentOS Linux release 7.6Docker: 24.0.5Prometheus: v2.37.6Consul: 1.6.1 docker 安装prometheus,alertmanage,说明一下这里直接将…

智慧农田使用的自动虫情测报灯的作用

TH-CQ3S随着科技的不断进步&#xff0c;智慧农业正在全球范围内兴起。作为智慧农业的重要组成部分&#xff0c;智慧农田已经成为提高农业生产效率、保障农产品质量安全的重要手段。而在智慧农田中&#xff0c;自动虫情测报灯的作用不可忽视。 自动虫情测报灯&#xff0c;顾名思…

C++大作业——学生选课系统优化版

C大作业——学生选课系统优化版 前言1.学生类和课程类的实现2.输入输出流重载的实现3.增删改查的实现4.多级菜单的实现5.选课和退选的实现5.完整代码 前言 本文是对本人之前写过的一个学生选课系统的优化&#xff0c;整体上的逻辑是和我上一篇博客一样的&#xff08;链接在此&a…

Linux学习(9)——RAID与服务器的常见故障

目录 一、服务器常见故障 1、系统不停重启进入不了系统 2、卡在开机界面右下角有fA B2 H8 3、系统安装不上 4、如何进入服务器的bios 5、一般进入阵列卡的快捷键 6.网络不通 7.硬盘不识别 二、RAID相关知识 1、RAID的概念 2、RAID功能实现 3、RAID实现的方式 三、常…

【算法系列 | 11】深入解析查找算法之—插值查找

序言 心若有阳光&#xff0c;你便会看见这个世界有那么多美好值得期待和向往。 决定开一个算法专栏&#xff0c;希望能帮助大家很好的了解算法。主要深入解析每个算法&#xff0c;从概念到示例。 我们一起努力&#xff0c;成为更好的自己&#xff01; 今天第11讲&#xff0c;讲…

RK3568 学习笔记 : 开机上电与串口波特率

前言 开发板&#xff1a;【正点原子】ATK-DLRK3568 开发板&#xff0c;包装什么的看上去有点高大上&#xff0c;也有点贵。。 开发板资料的 Linux-SDK 编译通过了&#xff0c;想尝试第一次上电开机&#xff0c;不过&#xff0c;开始出了一点状况&#xff0c;串口信息是乱码&am…

Python贪吃蛇小游戏(PyGame)

文章目录 写在前面PyGame入门贪吃蛇注意事项写在后面 写在前面 本期内容&#xff1a;基于pygame的贪吃蛇小游戏 实验环境 python3.11及以上pycharmpygame 安装pygame的命令&#xff1a; pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pygamePyGame入门 pygam…

国家信息安全水平等级考试NISP二级题目卷③(包含答案)

国家信息安全水平等级考试NISP二级题目卷&#xff08;三&#xff09; 国家信息安全水平等级考试NISP二级题目卷&#xff08;三&#xff09;需要报考咨询可以私信博主&#xff01; 前言&#xff1a; 国家信息安全水平考试(NISP)二级&#xff0c;被称为校园版”CISP”,由中国信息…