JVM-GC(G1)实践—GC异常定位、参数调整、GC更换

前言

如SpringBoot官方介绍所说的那样,从SpringBoot3.x开始支持的最低JDK版本为:JDK17(官方推荐使用BellSoft Liberica JDK),其对应的GC为G1。
SpingBoot
本文笔者从应用实践的角度出发,记录一些关于GC的一些实践总结。

1. G1简单回顾

1.1 jvm运行时数据区

先回顾一下JVM在运行时可能拥有的区域,即:JVM 运行时数据区(Run-Time Data Areas)。

在不同的JVM版本中所拥有的JVM运行时数据区可能是不同的,如在jdk8以前的永久代(PermGen)现已被元空间(Metaspace)替代。
JAVA-RuntimeDataAreas
jdk17中的JVM运行时数据区,分区如下:

  1. The pc Register(程序计数器)
  2. Java Virtual Machine Stacks(虚拟机栈)
  3. Heap(堆)
  4. Method Area(方法区)
  5. Run-Time Constant Pool(常量池)
  6. Native Method Stacks(本地方法栈)

其中与GC最相关的区域为Heap(堆),堆在所有线程中共享,存放每个线程运行时的数据,其对象的管理由 garbage collector(垃圾回收器)进行管理。

查看当前JDK默认使用的垃圾回收器可用java -XX:+PrintCommandLineFlags -version命令查看
java-print
如这里的-XX:+UseG1GC则代表此java默认使用的G1垃圾回收器

为了提高垃圾回收的效率,并减少垃圾回收对应用程序性能的影响,在JVM的堆中又分为新生代(Young Gen)和老年代(Old Gen)。
java-old-young
新生代中分为3个区:Eden、S0、S1,其默认大小比例为8:1:1。

可用jinfo -flag SurvivorRatio pid查看当前JVM的SurvivorRatio的数值,其代表Eden区是Survivor区大小的多少倍。

jinfo -flag SurvivorRatio 13236
jinfo

如这里显示--XX:SurvivorRatio为8,说明此JVM的Eden区大小是Survivor区域的8倍。Survivor区中又有两个大小相同的区域S0和S1,则Eden:S0:S1区域大小比例为8:1:1。

说到这里再提一下:JVM默认堆大小可用PrintFlagsFinal命令查看

以我的电脑为例,示例如下:

java -XX:+PrintFlagsFinal -version|findstr “MaxHeapSize”
java-maxHeapSize

将4211081216转为GB则约等于4GB,我这台电脑的物理内存为16G,即最大堆为物理内存的1/4

1.2 G1简介

自JDK9的正式发布,G1便成为了JDK9+的默认垃圾回收器,与之对应的《JEP 248: Make G1 the Default Garbage Collector》提案也算是有了一个正式的完结。

查阅javase官网《Garbage-First (G1) Garbage Collector》 ,可对G1有一个大致的了解。
g1

简单来说,G1所管理的堆区域由一系列区域(region)组成,每一个region是G1对JVM进行内存管理的最小单元。

需要注意的是,在G1中的年轻代老年代是动态分配的,即:同一个region,在不同的时刻可能属于不同的分代,如时刻A是属于年轻代,时刻B又属于老年代了。

与之对应的,在使用G1时以前的垃圾回收器(如 Serial GC、Parallel GC)中诸如NewRatio这类的参数在G1时也不再起作用,如仍需要指定老年代和年轻代的大小,可使用G1NewSizePercent、G1MaxNewSizePercent这类设置年轻代占整个堆大小比例的参数。

其他关于G1的介绍有点多,这里就不写了,详细可移步:HotSpot Virtual Machine Garbage Collection Tuning Guide

2. JDK常用监控工具回顾

为了便于我们进行故障排查与定位,在JAVA生态圈中有许多工具可对JVM的运行时数据进行观察分析,一些是JDK内置的,一些是第三方开发的。
各个工具的特点与适应场景会有所不同,为此我们就需要提前了解每个工具的特点,以在遇到问题时能快速做出选择,避免异常问题排查分析时间过久而造成业务的损失。

2.1 jconsole

在windows的JDK中,自带了一个强大GUI的JVM监控工具:jconsole.exe
此程序位于jdk\bin目录下,如:C:\Program Files\BellSoft\LibericaJDK-17\bin
jconsole
在jconsole中可以看到一些基础数据,如内存使用量、线程状态、MBean(被管理的对象),且支持远程JMX监控,对于一些基础的JVM观测需求基本够用

2.2 VisualVM

与jconsole类似的还有一个工具——VisualVM,但在JDK9开始VisualVM就不再默认包含在 JDK 的 bin 目录中,后续版本需要手动安装一下,项目地址为: https://github.com/oracle/visualvm
visualVM
VisualVM的界面和功能与jconsole相比要稍微丰富一些,与jconsole相比也更推荐用VisualVM,毕竟功能强大一点还更好看手动安装一下也是值得的。

2.3 jmap & jstack & jstat

上面介绍的JConsole和VisualVM都是具有GUI界面的监控工具,但在实际生产环境中,我们的java程序往往是运行在没有GUI界面的服务器环境中的,这时jmap、jsatck、jstat之前的则是一个不错的选择,毕竟命令行操作又快又简单。

工具主要功能适用场景
jmap导出内存快照(Heap Dump)、查看内存分布内存泄漏分析、对象分布查看
jstack导出线程快照(Thread Dump)调试线程死锁、高 CPU 占用等线程问题
jstat监控 JVM 性能指标(GC、类加载、内存使用)实时性能监控与性能调优

这3个工具的适用场景可参考上表,联合适用这3个工具也能做到VisualVM/JConsle的平替。

很久以前,我也曾写过《记一次java程序CPU占用过高问题排查》这篇博文解决过线上环境中的异常问题,其中用到的就是jstack。

命令记不住也没关系,使用 –help 参数可快速了解这些工具的具体用法。

2.4 JMX/JMI

JMI(Java Management Interface) 是 JMX(Java Management Extensions) 的前身,是 Java 提供的一个强大框架,主要用于管理和监控应用程序及其组件。如前面介绍的VisualVM/JConsle都支持JMX这种接口对远程的JVM进行监控,这在异常分析时非常有用。

这里以codecentric/spring-boot-admin:3.3.3中的jar包为例,使用windows上的VisualVM远程连接服务器上的JVM看一下效果:

将docker中的app.jar拷贝到linux主机(ip为192.168.6.66),再修改运行参数指定最大堆和最小堆均为80G,并开启jmx指定jmx端口为9010,运行命令如下:

java -Xms80G -Xmx80G \-Dcom.sun.management.jmxremote \-Dcom.sun.management.jmxremote.port=9010 \-Dcom.sun.management.jmxremote.rmi.port=9010 \-Dcom.sun.management.jmxremote.local.only=false \-Dcom.sun.management.jmxremote.authenticate=false \-Dcom.sun.management.jmxremote.ssl=false \-Djava.rmi.server.hostname=192.168.6.66 \-jar spring-boot-admin-app.jar

待jar启动成功后,在windows主机打开VisualVM并添加JMX连接:
JMX

VisualVM运行效果截图如下:
VisualVM-JMX

查看线程、堆、执行采样、JVM启动参数等功能一应俱全。

2.5 Arthas

arthas是一款由阿里贡献的jvm检测工具,它使用Attach技术可对正在运行中的java程序进行监测,功能强大使用简单,在生产环境中非常常用。

无论是容器环境还是物理机,arthas的使用都非常方便,下载jar并启动两步就搞定。示例:

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

Arthas

更多命令移步:《arthas命令列表》

另外还有一款功能更为强大的JVM监控分析工具——jprofiler,不过要收费,这里就不整理了。放个截图感受一下:
jprofiler

3. GC异常带来的问题/现象

3.1 OOM异常(示例代码)

我们都知道GC主要对JVM的堆空间进行管理,如果不能及时对堆空间进行回收,则在JVM再次分配空间时可能因为无法分配空间导致OOM异常。

写个让内存满的示例代码,看一下让堆被填满的效果:

import java.util.ArrayList;
import java.util.List;public class OutOfMemoryExample {public static void main(String[] args) {List<byte[]> list = new ArrayList<>();try {for (int i = 0; i < 10000; i++) {System.out.println("第" + i + "次执行");// 每次分配 1MB 的内存list.add(new byte[1024 * 1024]);}} catch (OutOfMemoryError e) {System.out.println("发生内存溢出异常: " + e.getMessage());}}
}

为加快OOM,使用-Xmx100m限制最大堆为100m,循环执行46次后发生了OOM,且应发了程序的异常退出:
java-oom
异常信息中也显示了OOM的原因为:Java heap space

3.2 程序卡顿/性能下降(示例代码)

程序的性能和gc往往也有着很大的关系,如频繁的gc则会导致原程序中性能的下降。写个示例代码演示下:


import java.util.ArrayList;
import java.util.List;public class GCPerformanceTest {public static void main(String[] args) {System.out.println("Starting test...");runTestWithFrequentGC(true);
//        runTestWithFrequentGC(false);}private static void runTestWithFrequentGC(boolean testGc) {System.out.println("Running test with frequent GC...");List<byte[]> memoryHog = new ArrayList<>();long startTime = System.currentTimeMillis();Thread computationThread = startComputationThread();if (testGc) {for (int i = 0; i < 10000; i++) {memoryHog.add(new byte[1024 * 1024]); // 分配1MB对象if (memoryHog.size() > 50) {memoryHog.clear(); // 清空列表,使对象变为垃圾,触发GC}}long endTime = System.currentTimeMillis();System.out.println("Frequent GC test took: " + (endTime - startTime) + " ms");}computationThread.interrupt();}private static Thread startComputationThread() {Thread thread = new Thread(() -> {long startTime = System.currentTimeMillis();long sum = 0;while (!Thread.currentThread().isInterrupted()) {for (int i = 1; i <= 1000000; i++) {sum += i;}}long endTime = System.currentTimeMillis();System.out.println("Computation thread finished. took: " + (endTime - startTime) + " ms");});thread.start();return thread;}
}

为演示方便给代码加上-Xmx256m -Xlog:gc参数,当运行runTestWithFrequentGC(true)时,因为代码频繁触发gc会导致ComputationThread的计算性能下降。示例结果如下:
java-gc-log
计算线程耗时为:1590ms,从输出的信息中可以看出发生了大量的gc。

而如果运行runTestWithFrequentGC(false),因为没有大量内存申请释放,则不会频繁触发gc,再看一下效果:
gc-test
ComputationThread线程中的计算任务在不到1ms就完成了计算,与前面的频繁gc代码相比,1ms VS 1590ms,性能差距一目了然。

4. GC异常定位与示例

关于GC异常定位的方式有很多种,这里记录较为常见的2种方式。

4.1 HeapDumpOnOutOfMemoryError

针对GC异常引发了OOM的这种场景,可以使用HeapDumpOnOutOfMemoryError方式自动导出jvm的堆栈。

这里以前面3.1章节中的示例代码场景为例,我们可在JAVA程序启动时添加以下参数,命令为:

java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump

示例:
jvm-dump-oom

之后再根据堆栈分析工具进行分析
hprof-view
如这里使用VisualVM对OOM时的堆栈进行分析,则可快速定位出是13行代码运行时发生了OOM。

list.add(new byte[1024 * 1024]);

对着代码一起看,则为:向list中再加入一个1MB的对象时JVM中确实放不下,最终就引发了OOM。

当然,对于上述示例这种场景我们也没有什么解决办法,要么加大堆空间延缓发生OOM的周期,要么就修改代码逻辑不让list填满或适当清理list中的成员。这就好比产品经理提了一个需求,让在1L的冰箱里装下一头100L大象,确实是办不到的。[程序员的无奈]

4.2 对运行的jvm进行分析

前面介绍的场景适用于OOM引起的异常分析,对于JVM能正常运行但性能不佳的场景,则需要用其他工具来进行定位分析。
以本文3.2章节中的场景为例,如发现计算线程的耗时较长,则可先用jstat命令进行初步查看:
jstat-log
如果发现GCT的值一直在变化,则说明一直在发生GC,再结合计算线程的耗时,则可初步判断是耗时较长是由GC引起的。

GCT为JVM 运行到当前时刻为止,所有 GC 事件(包括年轻代 GC、老年代 GC 和并发 GC)累计耗费的时间(单位:秒)

再结合jstack命令可协助定位
jstack-view

如果想进行详细分析,可将堆栈导出,命令为:jmap -dump:format=b,file=<heap_dump_file> <pid>

jmap
之后再使用工具对导出的hprof文件进行详细分析,如前面提到的VisualVM工具
VisulVM-hprof

5. GC参数调整与更换

在大多数的场景下GC的配置参数是不需要调整的,本文出于学习目的,可以了解一下需要调整GC参数的一些特殊场景。具体的场景有很多,这里列举两个。

5.1 固定对象过多场景

目标:向堆中存入4G大小的数据

代码如下:

import java.util.ArrayList;
import java.util.List;public class TestGC {public static void main(String[] args) throws InterruptedException {System.out.println("test start");List<byte[]> yongList = new ArrayList<>();for (int i = 0; i < 4000; i++) {System.out.println(i);// 分配1MB对象yongList.add(new byte[1024 * 1024]);}}
}

不修改任何参数,在物理内存为18G的电脑上以上代码,当输出3447后会因为OOM而导致程序退出,截图如下:
GC-OOM

这台电脑的物理内存为18G,最大堆默认为物理内存的1/4,18G * 1/4=4.5G;但存到3.4G时就发生了OOM,有没有觉得很疑惑?明明有4.5G的最大堆空间,才装了3.4G就发生了OOM

但如果我们修改代码,改为每次只存0.5MB,则又能成功运行:

import java.util.ArrayList;
import java.util.List;public class TestGC {public static void main(String[] args) throws InterruptedException {System.out.println("test start");List<byte[]> yongList = new ArrayList<>();for (int i = 0; i < 4000*2; i++) {System.out.println(i);// 分配1MB对象yongList.add(new byte[1024 * 512]);}}
}

截图如下:
TestGC

同样的目标,不同的写法,为何能产生不同的效果?

这是因为G1的堆是由许多region构成的。通过jcmd <pid> GC.heap_info参数进一步分析:
jcmd-gc-heap_info
每个Region的大小为: 4096K(4MB)

当我们每次存1M大小的byte到堆中,因为除了byte外还有额外数据,则会导致每个region则只能存下3个1M大小的byte。大致计算能存放1M大小的byte大致为:4500/4=1125*3=3375M

而当我们每次存512KB大小的byte到堆中,则每个regioin能存放的byte为3.5M,所以最终JVM堆中可以存入4G大小的byte

除了通过修改每次存放的byte大小可以解决上面的问题外,还可以通过调整GC参数来解决。如将每个region的大小修改为8M,让每个region可以装下更多的byte。
配置参数为:-XX:G1HeapRegionSize=8M
TestGC-Max-region
之后再次运行,4G大小的byte就装下啦~

5.2 大内存低时延场景

在G1中可使用-XX:MaxGCPauseMillis参数来调整每次GC所允许的最大暂停时间,默认为200ms。MaxGCPauseMillis值的大小影响着每次触发GC时需要回收的region数量,对于大多数应用场景默认的200ms不需要进行调整,但在有些对时延要求较高的场景则可以手动调整它。

除此之外,如遇到JVM运行内存较大的场景,也可以使用ZGC来代替G1,ZGC与G1相比在大内存场景下具有更低的stop-the-world停顿时间,官方说的是不超过1ms(without stopping the execution of application threads for more than a millisecond),最大支持16TB的大内存。(Shenandoah GC延时也很低,但目前还在开发中,按需选择)

这里我们用1台80G内存的服务器,写个简单的springboot-demo验证一下分别使用G1和ZGC的QPS数。接口代码如下:

import jakarta.annotation.PostConstruct;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;@RestController
public class TestController {private static final int _1MB = 1024 * 1024;private static final List<byte[]> OLD_GEN_HOLDER = new ArrayList<>();@PostConstructprivate void mockGC() {new Thread(() -> {System.out.println("开始内存分配实验...");int cycle = 0;while (true) {// 阶段1:快速填充老年代(每次循环分配2MB)// 2个1MB对象allocateOldGenObjects(2, 2 * _1MB);// 16MB对象allocateOldGenObjects(8, 16 * _1MB);// 阶段2:创建短期对象触发Young GC// 创建100个临时对象createShortLivedObjects(100);// 阶段3:监控内存状态monitorMemory(cycle++);// 控制循环速度try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}}}).start();}private void monitorMemory(int cycle) {Runtime rt = Runtime.getRuntime();long usedMB = (rt.totalMemory() - rt.freeMemory()) / _1MB;long maxMB = rt.maxMemory() / _1MB;// 当内存使用达到80%时尝试释放空间if ((double) usedMB / maxMB > 0.8) {System.out.printf("Cycle %d: 内存使用 %.1f%%, 触发空间释放%n",cycle, (usedMB * 100.0 / maxMB));// 释放部分老年代对象(防止OOM)OLD_GEN_HOLDER.subList(0, OLD_GEN_HOLDER.size() / 2).clear();System.gc(); // 建议触发GC(非必需但可能加速回收)}}private void createShortLivedObjects(int count) {// 创建短期存活对象触发Young GCfor (int i = 0; i < count; i++) {byte[] temp = new byte[2 * 1024]; // 2KB小对象}}private void allocateOldGenObjects(int count, int size) {for (int i = 0; i < count; i++) {// 大对象直接进入老年代(超过region 50%)OLD_GEN_HOLDER.add(new byte[size]);}}@GetMapping(value = "test")public String test() {int j = 0;for (int i = 0; i < 1000000; i++) {for (int x = 0; x < 300; x++) {j = j + i + x;}}return "welcome to https://blog.csdn.net/puhaiyang.j=" + j;}
}

先使用默认的JDK参数验证一下(taskset限制只用4核cpu):

taskset -c 0-3 java -Xmx80G -jar app.jar

默认情况下此JVM的GC使用的是G1,通过jstat -gc <PID> 1000命令可以看到有full gc发生
jstat-gc-pid

再使用wrk工具测试一下QPS,安装命令:sudo apt install wrk

测试命令(12个线程、400个用户、持续60秒):

wrk -t12 -c400 -d60s http://127.0.0.1:8080/test

wrk-g1

G1时,QPS显示为:12.91

再使用ZGC再次运行此程序:

taskset -c 0-3 java -XX:+UseZGC -Xmx80G -jar app.jar

测试截图为:
wrk-zgc

ZGC时,QPS显示为:17.29

查看GC情况:jps | awk '/app.jar/{print $1}' | xargs -I {} jstat -gc {} 1000

zgc-gc-log

从测试结果可知:大内存场景下,ZGC和G1相比确实有更低的时延和更大的吞吐量

6.总结

一直以来,JVM中的GC对于JAVA应用开发者的我们来说是一块黑盒,对于它的了解我们大多都是通过查阅各种资料获取,然而各种资料和翻译文章稂莠不齐,查看时记得考虑时效性注意甄别,并结合代码实践验证,对于大多数场景来说基本够用。

如果想要进一步探究GC的详细过程,则需要翻阅JDK的源码,适合熟悉C/C++大佬。

耳闻不如目见,目见不如足践。

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

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

相关文章

吾爱出品,文件分类助手,高效管理您的 PC 资源库

在日常使用电脑的过程中&#xff0c;文件杂乱无章常常让人感到困扰。无论是桌面堆积如山的快捷方式&#xff0c;还是硬盘中混乱的音频、视频、文档等资源&#xff0c;都急需一种高效的整理方法。文件分类助手应运而生&#xff0c;它是一款文件管理工具&#xff0c;能够快速、智…

修改Flutter工程中Android项目minSdkVersion配置

Flutter项目开发过程中&#xff0c;根据模板自动生成.android项目&#xff0c;其中app>build.gradle中minSdkVersion的值是19&#xff0c;但是依赖了一个三方库&#xff0c;它的Android sdk 最小版本只支持到21&#xff0c;运行报错如下&#xff1a; 我们可以手动修改.andro…

如何设计一个订单号生成服务?应该考虑那些问题?

如何设计一个订单号生成服务&#xff1f;应该考虑那些问题&#xff1f; description: 在高并发的电商系统中&#xff0c;生成全局唯一的订单编号是关键。本文探讨了几种常见的订单编号生成方法&#xff0c;包括UUID、数据库自增、雪花算法和基于Redis的分布式组件&#xff0c;并…

Java学习总结-Stream流

啥是Stream流&#xff1f; 用于操作集合或数组的数据。他就像把数据化为成一条河流&#xff0c;我们可以对这条流操作&#xff0c;例如过滤。 获取Stream流 Stream流的常用方法&#xff1a; Stream流的终结方法&#xff1a; 收集Stream流

《TypeScript 面试八股:高频考点与核心知识点详解》

“你好啊&#xff01;能把那天没唱的歌再唱给我听吗&#xff1f; ” 前言 因为主包还是主要学习js&#xff0c;ts浅浅的学习了一下&#xff0c;在简历中我也只会写了解&#xff0c;所以我写一些比较基础的八股&#xff0c;如果是想要更深入的八股的话还是建议找别人的。 Ts基…

热门面试题第14天|Leetcode 513找树左下角的值 112 113 路径总和 105 106 从中序与后序遍历序列构造二叉树 (及其扩展形式)以一敌二

找树左下角的值 本题递归偏难&#xff0c;反而迭代简单属于模板题&#xff0c; 两种方法掌握一下 题目链接/文章讲解/视频讲解&#xff1a;https://programmercarl.com/0513.%E6%89%BE%E6%A0%91%E5%B7%A6%E4%B8%8B%E8%A7%92%E7%9A%84%E5%80%BC.html 我们来分析一下题目&#…

Qt窗口控件之浮动窗口QDockWidget

浮动窗口QDockWidget QDockWidget 用于表示 Qt 中的浮动窗口&#xff0c;浮动窗口与工具栏类似&#xff0c;可以停靠在主窗口的上下左右位置&#xff0c;也可以单独拖出来作浮动窗口。 1. QDockWidget方法 方法说明setWidget(QWiget*)用于使浮动窗口能够被添加控件。setAllo…

Web前端之JavaScript的DOM操作冷门API

MENU 前言1、Element.checkVisibility()2、TreeWalker3、Node.compareDocumentPosition()4、scrollIntoViewIfNeeded()5、insertAdjacentElement()6、Range.surroundContents()7、Node.isEqualNode()8、document.createExpression()小结 前言 作为前端开发者&#xff0c;我们每…

【Linux-驱动开发-系统调用流程】

Linux-驱动开发-系统调用流程 ■ Linux-系统调用流程■ Linux-file_operations 结构体 ■ Linux-系统调用流程 ■ Linux-file_operations 结构体 在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体&#xff0c;此结构体就是 Linux 内核驱动操作函数集…

ToolsSet之:ASCII字符表和国际标准代码表

ToolsSet是微软商店中的一款包含数十种实用工具数百种细分功能的工具集合应用&#xff0c;应用基本功能介绍可以查看以下文章&#xff1a; Windows应用ToolsSet介绍https://blog.csdn.net/BinField/article/details/145898264 ToolsSet中Other菜单下的ASCII Table是一个ASCII…

C语言判断闰年相关问题

一、简单闰年问题引入 写一个判断年份是否为闰年的程序? 运行结果: 二、闰年问题进阶 使用switch语句根据用户输入的年份和月份,判断该月份有多少天? 第一种写法(判断年份写在switch的case的里面): 运行结果: 第二种解法(先判断闰年): 运行结果: 三、补充 switch中的ca…

基于Java的班级事务管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 随着世界经济信息化、全球化的到来和电子商务的飞速发展&#xff0c;推动了很多行业的改革。若想达到安全&#xff0c;快捷的目的&#xff0c;就需要拥有信息化的组织和管理模式&#xff0c;建立一套合理、畅通、高效的线上管理系统。当前的班级事务管理存在管理效率低下…

javaweb后端登录功能cookie session

登录功能 只需要这几个&#xff0c;用原来的返回太多用不上的信息&#xff0c;新写一个类只返回登录的结果 Ctrli 实现service的方法 和mapper相关的起名不用和业务一样 登录校验 登录校验思路 会话技术 cookie 创建cookie对象&#xff0c;响应给浏览器 服务端设置的…

《可爱风格 2048 游戏项目:HTML 实现全解析》

一、引言 在如今的数字化时代&#xff0c;小游戏以其简单易上手、趣味性强的特点深受大家喜爱。2048 游戏作为一款经典的数字合并游戏&#xff0c;拥有庞大的玩家群体。本文将详细介绍一个用单文件 HTML 实现的可爱风格 2048 游戏项目&#xff0c;它不仅具备传统 2048 游戏的基…

UART转APB模块ModelSim仿真

一、简介 之前介绍过一个UART转AHB模块&#xff0c;这个代码的框架有个好处&#xff0c;就是FPGA内总线接口比较容易修改成其他总线接口。下图是UART转AHB模块中子模块uart_ahb_mst的框图&#xff0c;主要有三个状态机&#xff1a; &#xff08;1&#xff09; UART_RX_FSM将接收…

ReAct: Synergizing Reasoning and Acting in Language Models

https://zhuanlan.zhihu.com/p/624003116https://zhuanlan.zhihu.com/p/624003116https://github.com/apssouza22/ai-agent-react-llm/tree/main

尝试在软考62天前开始成为软件设计师-信息系统安全

安全属性 保密性:最小授权原则(能干活的最小权限)、防暴露(隐藏)、信息加密、物理保密完整性(防篡改):安全协议、校验码、密码校验、数字签名、公证 可用性:综合保障( IP过滤、业务流控制、路由选择控制、审计跟踪)不可抵赖性:数字签名 对称加密 DES :替换移位 3重DESAESR…

IPv4向IPv6过渡

主要有三种过渡技术 隧道技术&#xff1a;用于解决IPv6节点之间通过IPv4网络进行通信的问题协议翻译技术&#xff1a;使纯ipv6节点与纯Ipv4节点之间进行通信双协议栈技术&#xff1a;使ipv4与ipv6可以共存于同一台设备和同一个网络中 隧道技术 把ipv6分组封装到Ipv4分组中&a…

算法题(107):function

审题&#xff1a; 本题需要我们根据题目写出递归函数&#xff0c;并返回递归结果 时间复杂度&#xff1a;本题的数据范围虽然很大&#xff0c;但是由于条件2的限制&#xff0c;数据量可以看成是20&#xff0c;于是我们就可以使用递归函数了 思路&#xff1a; 方法一&#xff1a…

【江协科技STM32】BKP备寄存器RTC实时时钟(学习笔记)

BKP备寄存器 BKP简介 BKP&#xff08;Backup Registers&#xff09;备份寄存器BKP可用于存储用户应用程序数据。当VDD&#xff08;2.0~3.6V&#xff09;电源被切断&#xff0c;他们仍然由VBAT&#xff08;1.8~3.6V&#xff09;维持供电。当系统在待机模式下被唤醒&#xff0…