JAVA调优

1 JAVA虚拟机

1.1 基本组成

通常来说Java平台标准版(Java SE)包括 Java SE开发工具包(JDK)和Java SE运行时环境(JRE)。

JRE提供了运行以Java编程语言编写的applet和应用程序所必需的库,Java虚拟机和其他组件;JDK包括JRE以及编译器和调试器等命令行开发工具,可以用来开发Java应用程序 。

虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务,在 HotSpot 虚拟机中直接将虚拟机栈和本地方法栈合二为一。

1)对象创建

在java程序中,创建对象的方式有多种。最常用的方式是new语句,还可以通过反射机制、Object.clone方法、反序列化以及Unsafe.allocateInstance方法来新建对象。

(1)类加载过程

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

new对象创建过程:

(2)内存分配

对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

指针碰撞:

  • 场景:Java堆中内存是绝对规整的;
  • 原理:所有用过的内存都放在一边,空闲的内存放在另外一边,中间放一个指针作为分界点的指示器,分配内存时只需要把那个指针向空闲空间那边挪动一段与对象大小相等的距离就可以了;
  • GC收集器:Serial、ParNew等带Compact过程的收集器。

空闲列表:

  • 场景:Java堆中内存不是规整的;
  • 原理:虚拟机会维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录;
  • GC收集器:CMS基于Mark-Sweep算法的收集器。

在Hotspot虚拟机里,对象在堆里的存储布局主要分为三部分对象头,实例数据和对象填充。

2)对象访问

Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种。

(1)使用句柄

Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。

(2)直接指针

Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。

1.2 堆栈分配

应用程序的内存分配: 

堆的详细结构: 

  • 对象优先在Eden区进行分配,如果Eden区满了之后会触发一次Minor GC大部分对象在Eden区中生成。
  • Minor GC之后从Eden存活下来的对象将会被移动到S0区域,当S0内存满了之后又会被触发一次Minor GC,S0区存活下来的对象会被移动到S1区,S0区空闲;S1满了之后再Minor GC,存活下来的再次移动到S0区,S1区空闲,这样反反复复GC,他的年龄会增长1,到达15的时候,这些对象就会移步到老年代。(年龄阈值可以通过参数 -XX:MaxTenuringThreshold设置)
  • 持久代用于存放静态文件,如今Java类、方法等。JDK1.8之后没有持久代,变为元空间了。

Full GC触发机制
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

Major GC是清除老年代,往往Major GC发生一次就会发生Minor GC,所以也叫FULL GC。FULL GC 的速度往往会比 Minor GC 慢 10 倍。

1.3 JVisualVM

JVisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈。

demo1

public class HeapTest {
    byte[] a = new byte[1024 * 100];    //100kb

    public static void main(String[] args) throws Exception{
        ArrayList<HeapTest> heapTests = new ArrayList<>();
        while(true){
            heapTests.add(new HeapTest());
            Thread.sleep(10);
        }
    }
}

运行程序跟工具:

note 插件安装:

地址:VisualVM: Plugins Centers

(1)选择版本

(2)选择插件下载

(3)安装插件

将“GC Roots”对象作为起点从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象

GC Roots根节点: 线程栈的本地变量、静态变量、本地方法栈的变量等等

2 Arthas调优工具

Arthas 是阿里巴巴开源的一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

运行后会列出所有的进程 

常用命令:

1)dashboard,按ctrl+c可以中断执行

2)thread 1会打印线程 ID 1 的栈,通常是 main 函数的线程。

3) jad 来反编译

4)其他命令

命令列表 | arthas

3 垃圾收集器

3.1 基础介绍

什么是垃圾?

一个对象没有任何引用指向他,那他就认作是需要回收的垃圾对象。

如何寻找垃圾对象?

1)引用计数法:对每一个对象保存一个整型的引用计数器属性,用于记录对象被引用的数量。对象的引用计数器的值为0,即表示对象A不能在被使用,可进行回收。

        缺点:(1)他需要单独的字段存储计数器,这样的做法增加了存储空间的开销。

                   (2)每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。

                   (3)引用计数器还有一个严重的问题,即无法处理循环引用的问题,这是一条致命的缺陷,导致在Java回收的垃圾回收器中没有使用这类算法。

2)GC roots 可达性分析算法

根可达:判断一个对象到 Gc Roots这些根对象之间存不存在引用链,存在的话,不回收,不存在的话,回收掉即可。

GC Roots 根对象:

1、虚拟机栈中引用的对象;开发人员写的方法里面的变量指向的对象;比如:Deque<Integer> numbers = new LinkedList<>();
2、方法区中的静态属性引用的对象;类中定义的静态的类变量引用的对象;例如:private static String s1 = "Java";
3、本地方法栈中引用的对象,native 方法里面的变量指向的对象
4、Java 虚拟机内部的引用,比如基本数据类型对应的 Class 对象; JVM 里面常驻的异常对象,比如空指针异常 NullPointException;
5、被同步锁 synchornized 持有的对象;

如何清楚垃圾对象?

1)Mark-Sweep (标记清除)


2)Copying (拷贝)


3)(标记压缩)Mark-Compact

3.2 垃圾回收器 

新生代的垃圾回收器共有三个:Serial,Parallel Scavenge 和 Parallel New。这三个采用的都是标记 - 复制算法。

  • Serial 是一个单线程的,Parallel New 可以看成 Serial 的多线程版本。Parallel Scavenge 和 Parallel New 类似,但更加注重吞吐率。此外,Parallel Scavenge 不能与 CMS 一起使用。

老年代的垃圾回收器也有三个:Serial Old 和 Parallel Old,以及 CMS。

  • Serial Old 和 Parallel Old 都是标记 - 压缩算法。同样,前者是单线程的,而后者可以看成前者的多线程版本。

G1(Garbage First)是一个横跨新生代和老年代的垃圾回收器。

  • 由于 G1 的出现,CMS 在 Java 9 中已被废弃。G1每个区域都可以充当 Eden 区、Survivor 区或者老年代中的一个。它采用的是标记 - 压缩算法,而且和 CMS 一样都能够在应用程序运行过程中并发地进行垃圾回收。G1 能够针对每个细分的区域来进行垃圾回收。在选择进行垃圾回收的区域时,它会优先回收死亡对象较多的区域。

java 11 引入了 ZGC,宣称暂停时间不超过 10ms。

1)Serial 

  • 进行垃圾收集工作的时候,其他线程都必须暂停直到垃圾收集结束(Stop The World)。
  • 在并行能力较弱的单CPU环境下往往表现优于其他收集器。

2)ParNew 

ParNew收集器是Serial收集器的多线程版本;除了使用了多线程进行垃圾收集以外,其他的都和Serial一致;它默认开始的线程数与CPU的核数相同,可以通过参数-XX:ParallelGCThreads来设置线程数。

3)Parallel Scavenge

Parallel Scavenge收集器依然是个采用复制算法的多线程新生代收集器,它与其他的收集器的不同之处在于它主要关心的是吞吐量。

吞吐量=用户线程执行时间/(用户线程执行时间+垃圾收集时间)

4)Serial Old

5)Parallel Old

6)CMS 

CMS收集器是一种以获取最短回收停顿时间为目标的收集器,在互联网网站、B/S架构的中常用的收集器就是CMS。

缺点:

  • 1、当系统的 cpu 核心数比较少的时候,对于整个系统的资源的开销是比较大的;CMS 默认启动的垃圾回收的核心线程数目:(处理器核心数量 + 3)/ 4;
  • 2、CMS 没有办法处理浮动垃圾;当 CMS 在并发标记以及并发清理的时候,由于程序在继续的运行,所以任然在产生垃圾,但是这一部分的垃圾是在标记之后产生的,所以在此次的垃圾回收中,是不会被清理的,只能在下一次的垃圾回收中清理这些浮动垃圾;
  • 3、该垃圾收集器本身是基于 标记 - 清除 算法的,所以存在内存空间碎片的问题;在一定程度上面是需要整理内存碎片的;

5)Garbage First (G1 回收器)

G1是一款面向服务端应用的垃圾回收器。

  • 并行与并发:与CMS类似,充分里用多核CPU的优势,G1仍然可以不暂停用户线程执行垃圾收集工作
  • 分代收集:分代的概念依然在G1保留,当时它不需要和其他垃圾收集器配合使用,可以独立管理整个堆内存
  • 空间的整合:G1整体上采用的是标记-整理算法,从局部(Region)采用的是复制算法,这两种算法都意味着G1不需要进行内存碎片整理
  • 可预测的停顿:能够让用户指定在时间片段内,消耗在垃圾收集的时间不超过多长时间。

3.3 GC设置参数

参数描述
-XX:+UseSerialGC启用串行收集器
-XX:+UseParallelGC启用并行收集器,配置了该选项,那么 - XX:+UseParallelOldGC 默认启用
-XX:+UseParallelOldGCFullGC 采用并行收集器,默认禁用,如果设置 - XX:+UseParallelGC 则启用
-XX:+UseParNewGC年轻代采用并行收集器,如果设置了 - XX:+UseConcMarkSweepGC,自动启用
-XX:ParallelGCThreads年轻代及老年代垃圾回收使用的线程数,默认是 JVM 使用的 CPU 个数
-XX:+UseConcMarkSweepGC对于老年代,启用 CMS 垃圾收集器,当并行收集器无法满足应用的延迟需求时,推荐使用 CMS 或 G1 收集器,启用该选项后,-XX:+UseParNewGC 自动启用;可以使用-XX:ConcGCThreads设置并发线程数量。
-XX:+UseG1GC启用 G1 收集器,G1 是服务器类型的收集器,用于多核,大内存的机器,它在保持高吞吐量的情况下,高概率满足 GC 暂停时间的目标

-XX:+PrintGC

输出GC日志
-XX:+PrintGCDetails输出GC的详细日志
-XX:+PrintGCTimeStamps输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps输出GC的时间戳
-XX:+PrintHeapAtGC在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log日志文件的输出路径

4 JVM调优

JVM调优主要是优化FULL GC:

  • 第一:调整 JVM 参数进行优化
  • 第二:GC 回收器优化

4.1 JVM参数

参数参数作用初始值
-server启动 Server,以服务端模式运行
-Xms最小堆内存物理内存的1/64(<1GB)
-Xmx最大堆内存物理内存的 1/4(<1GB)
-Xmn设置年轻代大小(for 1.3/1.4),注意:此处的大小是(eden+ 2 survivor space)。与 jmap -heap 中显示的 New gen 是不同的。整个堆大小=年轻代大小 + 老年代大小 + 持久代(永久代)大小.增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8
-XX:NewSize设置年轻代大小(for 1.3/1.4)
-XX:MaxNewSize年轻代最大值(for 1.3/1.4)
-Xss每个线程的堆栈大小
-XX:MetaspaceSize元空间初始值
-XX:MaxMetaspaceSize元空间最大内存
-XX:MaxNewSize新生代最大内存
-XX:NewRatio年轻代和老年代大小比值,取值为整数,默认为 2
-XX:SurvivorRatioEden 区与 Survivor 区大小比值,取值为整数,默认为 8

Tomcat 常见的几个 JVM 异常:

  • 第一:OutOfMemoryError:Java heap space,表示堆内存满了

-Xms512m        -Xmx2048m

  • 第二:OutOfMemoryError:PermGen space,表示静态内存区满了

-XX:PermSize=50m        -XX:MaxPermSize=50m

  • 第三:StackOverflowError,表示栈内存溢出

检查代码是否陷入死循环,导致内存溢出

5 系统调优

5.1 中间件优化

如果并发量很高的情况,光靠分布式架构是解决不了的,我们还得集成一些高性能的中间件来提高系统的吞吐量。常见的中间件:

序号中间件解决什么问题
1Redis分布式缓存
2ActiveMQ、RabbitMQ、Kafka、RocketMQ消息队列
3Solr、ElasticSearch全文检索
4Elastic-Job、Xxl-Job分布式定时器
5Netty高性能通讯框架,比如:开发一个百万级推送系统、弹幕系统
6MongoDB、Hbasenosql数据库,缓解MySQL数据库的压力
7MyCat、Sharding-JDBCMySQL数据库的分库分表

5.2 JAVA代码优化

1)HashMap、HashTable、ConcurrentHashMap的区别

  • HashMap线程不安全,HashTable线程安全,ConcurrentHashMap基于分段锁实现的线程安全的。如果高并发下可以使用ConcurrentHashMap来代替HashMap。
Map<String,String> map=new HashMap<>(); //线程不安全
Map map=Collections.synchronizedMap(new HashMap());//线程安全

2)在海量元素中(例如:10亿无序、不定长、不重复)快速判断一个元素是否存在

  • 使用布隆过滤器(Bloom Filter)

3)IO合理使用

  • 如果使用传统的BIO(也就是IO),它是面向流的,那么尽量使用带缓冲的,Buffer开头的;
  • 如果操作本地IO,则尽量使用NIO,它是面向缓冲区的,直接缓冲区、非直接缓冲区、零拷贝合理的选择使用;
  • 网络通信的话,基于Netty框架去实现,它的底层的NIO(同步非堵塞的),相比BIO(同步堵塞)性能要高很多。

4)并发编程

5)SQL语句性能优化

6)场景设计思路

(1)案例1:如果用户下单成功,15分钟未付款,自动取消订单

  • 方案:定时器、Redis监听、消息队列的延迟队列。后面两种的方案的性能肯定比定时器要高。

(2)案例2:比如登录的时候通过短信验证码进行验证,60s只能不能重复发送,验证码有效期5分钟        

  • 方案1:依赖数据库实现,数据库创建相应的表即可。
  • 方案2:依赖Redis实现,每次发送验证码则保存到Redis

(3)案例3:从Excel读取并且导入100万条数据

  • 方案:解析Excel,解析每一行则先放入本地队列里面,另外一个线程不断监听Queue,获取到Queue的数据的时候往消息队列发送,由消息队列的消费端负责处理。消费端可以批量插入数据库;也可以先把数据存入Redis后期再通过同步工具同步到数据库。

5.3 运维

性能提升方案:

  • Tomcat的性能调优、集群部署;Tomcat负载方案可以有:Nginx、LVS、硬件负载等等
  • MySQL性能调优、主从复制集群、基于主从复制实现读写分离及负载均衡等等;使用中间件实现分库分表。
  • 静态缓存,比如一些静态资源可以往前挪,比如说原来html页面的图片是存储在Tomcat里面的,可以把它缓存在Nginx、客户端本地、CND等。基于Nginx分发到CND和OSS,而不是每次都从后端服务器获取,提高性能。

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

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

相关文章

CodeTON Round 7 (Div. 1 + Div. 2, Rated, Prizes!)

B. AB Flipping 老规矩&#xff0c;自己要模拟一遍样例&#xff0c;发现样例还不够&#xff0c;就自己构造样例&#xff0c;这样做着做着就会有思路。 分析&#xff1a;假如现在有这样一个字符串 BBBAABABBAAA。会发现前三个和后三个一定是不会被操作的&#xff0c;因为不会满…

5、DMA Demo(STM32F407)

DMA简介 DMA 全称Direct Memory Access&#xff0c;即直接存储器访问。 DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作&#xff0c;传输动作本身是由DMA控制器来实现和完成的。 DMA传输方式无需CPU直接控制传输&#xff0c;也没有中断处理方式那…

力扣295. 数据流的中位数(java,堆解法)

Problem: 295. 数据流的中位数 文章目录 题目描述思路解题方法复杂度Code 题目描述 思路 由于该题目的数据是动态的我们可以维护两个堆来解决该问题 1.维护一个大顶堆&#xff0c;一个小顶堆 2.每个堆中元素个数接近n/2&#xff1b;如果n是偶数&#xff0c;两个堆中的数据个数…

SpringCloud核心组件

Eureka 注册中心&#xff0c;服务的注册与发现 Feign远程调用 Ribbon负载均衡&#xff0c;默认轮询 Hystrix 熔断 降级 Zuul微服务网关&#xff08;这个组件负责网络路由&#xff0c;可以做统一的降级、限流、认证授权、安全&#xff09; Eureka 微服务的功能主要有以下几…

LLM之Agent(二):BabyAGI的详细教程

BabyAGI是一个 AI 支持的任务管理系统&#xff08;Python脚本&#xff09;&#xff0c;使用 OpenAI 和 Pinecone API 创建, 优先级排序和执行任务。该系统背后的主要思想是基于先前任务的结果和预定义的目标创建任务。脚本然后使用 OpenAI 的自然语言处理&#xff08;NLP&#…

数据结构学习笔记——二叉树的遍历和链式存储代码实现二叉树

目录 一、二叉树的遍历&#xff08;一&#xff09;二叉树的先序遍历&#xff08;DLR&#xff09;&#xff08;二&#xff09;二叉树的中序遍历&#xff08;LDR&#xff09;&#xff08;三&#xff09;二叉树的后序遍历&#xff08;LRD&#xff09;&#xff08;四&#xff09;二…

【古月居《ros入门21讲》学习笔记】07_创建工作空间和功能包

目录 说明&#xff1a; 1. 工作空间(workspace) 结构&#xff1a; 2. 创建工作空间和功能包 创建工作空间 编译工作空间 创建功能包 设置环境变量 3. 注意 同一个工作空间下&#xff0c;不能存在同名的功能包&#xff1b; 不同工作空间下&#xff0c;可以存在同名的功…

学习程序员必知必会的基础算法(收藏)

近年来学习python的程序员愈来愈多&#xff0c;有的同学选择了python培训机构&#xff0c;也有的人觉得自己天赋好选择了自学不管大家怎么去学习&#xff0c;在学习python基础的过程中&#xff0c;肯定离不开的就是基础算法&#xff0c;今天就为大家介绍几大学习中的基础算法。…

ffmpeg开发 环境配置

ffmpeg开发简图 1 下载ffmpeg开发包 https://ffmpeg.org/download.html 包含三个版本&#xff1a;Static、Shared以及Dev Static --- 包含3个应用程序&#xff1a;ffmpeg.exe , ffplay.exe , ffprobe.exe&#xff0c;体积都很大&#xff0c;相关的DLL已经被编译到exe里面去…

TDA4VM EVM开发板调试笔记

文章目录 1. 前言2. 官网资料导读3. 安装 Linux SDK4. 制作SD 启动卡5. 验证启动1. 前言 TDA4作为一般经典的车规级SOC芯片,基于它的低阶智驾方案目前成为各家智驾方案公司的量产首选,这也使得基于TDA4的开发需求陡增,开发和使用TDA4既要熟悉Linux驱应用开发,还要熟悉传统…

自建CA实战之 《0x02 Nginx 配置 https双向认证》

自建CA实战之 《0x02 Nginx 配置 https双向认证》 上一章节我们已经实现了Nginx上配置https单向认证&#xff0c;主要场景为客户端验证服务端的身份&#xff0c;但是服务端不验证客户端的身份。 本章节我们将实现Nginx上配置https双向认证&#xff0c;主要场景为客户端验证服…

基于Java SSM框架+Vue实现汉服文化平台网站项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架Vue实现汉服文化平台系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个汉服文化平台网站 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将…

在gazebo里搭建一个livox mid360 + 惯导仿真平台测试 FAST-LIO2

在gazebo里搭建一个livox mid360 惯导仿真平台测试 FAST-LIO2 前言立方体平台加入 livox mid360 激光雷达加入IMU模块调整底盘大小 并设计调用接口测试 Fast-Lio2 前言 livox mid360 在官网一直没有货&#xff0c;在gazebo里可以仿真该雷达形式的点云。 但是其只发布雷达的数…

【电源专题】DC/DC电源FB分压电阻设计注意事项

在DC/DC电源中我们不可避免的会遇到FB分压电阻的取值,PCB设计等问题。如下所示随意打开一份同步降压稳压器规格书TPS56320X,规格书中的简化电路原理图就已经存在VFB管脚上的两个分压电阻。 很多工程师朋友们会误认为分压电阻只是简单的将输出电压缩小到参考电压,通过此电压来…

电子学会C/C++编程等级考试2023年03月(三级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:和数(2023.3) 给定一个正整数序列,判断其中有多少个数,等于数列中其他两个数的和。 比如,对于数列1 2 3 4, 这个问题的答案就是2, 因为3 = 2 + 1, 4 = 1 + 3。 时间限制:10000 内存限制:65536输入 共两行,第一行是数列中…

【接口自动化】selenium库也有大用场(获取cookie)

相信有些童鞋在做接口、或者说接口自动化测试的过程中会遇到这样的场景&#xff1a;测试的接口&#xff0c;必须是需要登录后才能发起请求成功的。 那么怎么解决呢&#xff1f; 本着团队协作的精神&#xff0c;我们就去让开发同学开个后门&#xff0c;给你个“万能”值&#x…

AntDB“超融合+流式实时数仓”——颠覆50年未变的数据库内核

流式处理引擎&#xff0c;颠覆50年未变的数据库内核 流式处理的概念 2001年9月11日&#xff0c;美国世贸大楼被袭击&#xff0c;美国国防部第一次将“主动预警”纳入国防的宏观战略规划。而IBM作为当时全球最大的IT公司&#xff0c;承担了大量基础支撑软件研发的任务。其中200…

R语言单因素方差分析+差异显著字母法标注+逐行详细解释

R语言单因素方差分析 代码如下 df <- read.csv("data.csv",header TRUE,row.names 1) library(reshape2) df <- melt(df,idc()) names(df) <- c(trt, val) df aov1 <- aov(val~trt,datadf) summary(aov1)library(agricolae) data <- LSD.test(aov…

双指针算法总结

双指针算法分为两类&#xff1a;第一类指向一个序列&#xff08;更多的情况&#xff09;&#xff0c;第二类指向两个序列。 基本的代码框架是&#xff1a; for (i 0, j 0; i < n; i) {while (j < i && check(i, j)) j;// 每道题目的具体逻辑 } 核心思想&…

探索使用Quarkus和MicroProfile 构建Kubernetes原生微服务的秘诀!

Kubernetes Native Microservices with Quarkus and MicroProfile 是一个基于Kubernetes原生微服务的开发框架&#xff0c;它结合了Quarkus和MicroProfile的优点&#xff0c;提供了一个高效、可扩展、易于管理的微服务解决方案。 Quarkus是一个针对Java虚拟机&#xff08;JVM&…