当前位置: 首页 > news >正文

编译内联导致内存泄漏的问题定位修复

作者:0x264

问题

线上长时间存在一个跟异步 inflate 相关的量级较大的内存泄漏,如下所示:

第一次分析

从内存泄漏粗略看有几个信息:

  1. 被泄漏的Activity有很多,所以可能跟某个具体业务的关系不大
  2. 引用链特别短,并且可以看出 gc root 是 Java Frame 中的BasicInflater实例,然后它通过 mContext 字段持有了 Activity

从上面的这个信息推测导致内存泄漏的原因是:

  1. 业务代码触发了一些布局的异步 Inflate
  2. 当前页面退出,destroy
  3. 之前发出的异步 Inflate 请求还没有执行完,还在子线程中inflate,所以会短暂的持有 context
  4. 如果这个时候dump hprof 分析,就会发现 context(activity)泄漏了
public void runInner() {  InflateRequest request;  try {  request = mQueue.take();  } catch (InterruptedException ex) {  // Odd, just continue  Log.w(TAG, ex);  return;  }  // 子线程需要把 mQueue 里面的 request 处理完,而request 持有 inflater,inflater 持有 contexttry {  request.view = request.inflater.mInflater.inflate(  request.resid, request.parent, false);  } catch (RuntimeException ex) {  // Probably a Looper failure, retry on the UI thread  Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"  + " thread", ex);  }  Message.obtain(request.inflater.mHandler, 0, request)  .sendToTarget();  
}  @Override  
public void run() {  while (true) {  runInner();  }  
}

根据上面的分析,这种泄漏理论上是存在的,但是 inflate 一个layout一般很快,几毫秒、几十毫秒、最多几百毫秒,这种属于短时泄漏,而且时间特别短,影响不大,所以第一次简单看了下这个问题后觉得影响不大不必处理😂

第二次分析

虽然上面判断这个“影响不大,且泄漏时间很短”,但是每个版本都会触发报警,而且是量级很大的泄漏,于是继续排查一下这个泄漏是否有其他原因~

从内存泄漏的量级看,之前的判断似乎有点说不通:

  1. 泄漏时间窗口这么短,hprof 刚好就在这期间dump的概率极低,量怎会这么大?
  2. 短时泄漏问题其实很多,比如业务 postDelay 一个几秒的Runnable(持有外部类引用),在这期间 activity destroy了,也会出现短时泄漏,这个时间几秒,比 inflate 要长多了,而且业务上这类情况很多,但是线上抓到的这类情况很少。

因此第一次分析的情况似乎不太对,于是捞个 hprof 再分析一下看看:

我们先通过内存信息来验证下我们第一次分析的猜测原因(activity destroy的时候,异步 inflate 任务还没执行完成)是否正确:

  1. 看下 InflateThread 的 mQueue 中还有多少 InflateRequest 待处理:

捞了很多个hprof,结果都是如此令人惊讶:InflateRequest 队列都是空的,里面没有任务待处理!!!

  1. 再一次猜想:会不会是每次dump hprof的时候,刚好最后一个 InflateRequest 被从队列中取出来了,但是还没有执行完成呢?其实想想这种概率已经不能再低了,但是目前也没有其他怀疑点,换个角度看,假设是这种情况,会不会是某个布局有点问题,导致 inflate 耗时特别长,然后增加了被抓到的概率呢?

先来看下那个持有activity的 BasicInflater 信息:

我们知道LayoutInflater在inflate开始前会把当前要用的context存到他的 mConstructorArgs[0] 中,inflate 完成后再把 mConstructorArgs[0] 恢复,可以参考如下代码:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {  synchronized (mConstructorArgs) {  // ......Context lastContext = (Context) mConstructorArgs[0];  mConstructorArgs[0] = inflaterContext;  try {// ...... do inflate} finally {  // Don't retain static reference on context.  mConstructorArgs[0] = lastContext;  mConstructorArgs[1] = null;  Trace.traceEnd(Trace.TRACE_TAG_VIEW);  }  return result;  }  
}

也就是说如果正在 inflate,mConstructorArgs[0]应该持有context,但是我们看到这个时候 BasicInflater 中的 mConstructorArgs 的2个element都是 null,也就是说当前它处于空闲状态,并非正在 inflate!!!

初步结论

根据目前的信息来看,泄漏的Activity是被一个空闲的 BasicInflater 持有的。

进一步排查,发现一个更奇怪的现象:在dump出来的 hprof中,AsyncLayoutInflater$BasicInflater 的实例数始终比 AsyncLayoutInflater 刚好多一个,而多出来的那一个就是导致 activity 泄漏的那个实例

从代码上看,AsyncLayoutInflater$BasicInflater 都是在 AsyncLayoutInflater的构造函数中创建的,按理说AsyncLayoutInflater$BasicInflater不会比AsyncLayoutInflater更多才对🤔️

public AsyncLayoutInflater(@NonNull Context context) {  mInflater = new BasicInflater(context);  mHandler = new Handler(mHandlerCallback);  mInflateThread = InflateThread.getInstance();  
}

难道导致泄漏的这个 BasicInflater 是其他地方创建出来的? 首先反射创建不大可能,因为这个类没有keep,那么最有可能的就是下面这个路径了:

@Override  
public LayoutInflater cloneInContext(Context newContext) {  return new BasicInflater(newContext);  
}

而这个方法似乎只在ViewStub中使用:

// layoutinflater
public final View createView(@NonNull Context viewContext, @NonNull String name,  @Nullable String prefix, @Nullable AttributeSet attrs)  throws ClassNotFoundException, InflateException {  // ... final ViewStub viewStub = (ViewStub) view;  viewStub.setLayoutInflater(cloneInContext((Context) args[0]));  // ...
}

这个clone出来的 inflater 会被 ViewStub.mInflater 持有,但是从内存数据来看,它自己是gc root,并且没有其他对象持有它

而这个 BasicInflater 之所以是 gc root,是因为它是在当前Java frame的本地变量表中,再回头看一下相关代码:

public void runInner() {  InflateRequest request;  try {  request = mQueue.take();  } catch (InterruptedException ex) {  // Odd, just continue  Log.w(TAG, ex);  return;  }  try {  request.view = request.inflater.mInflater.inflate(  request.resid, request.parent, false);  } catch (RuntimeException ex) {  // Probably a Looper failure, retry on the UI thread  Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"  + " thread", ex);  }  Message.obtain(request.inflater.mHandler, 0, request)  .sendToTarget();  
}  @Override  
public void run() {  while (true) {  runInner();  }  
}
第二次猜想

倘若 runInner()被内联到 run()中,那么正好就是这个情况,并且能解释上面的所有现象,于是找了个线下包看了下,结果惊呆了:没有内联。。。

无奈,似乎没有思路了,但是也不能不处理啊,翻了下业务代码后发现有个LiveAsyncLayoutInflater 它就是从 AsyncLayoutInflater中copy出来的,唯一的改动就是在 runInner() 中,取出 request 执行 inflate之前先判断一下 context(activity)是否已经destroy,如果是的话,直接丢弃这个 request。跟这个代码的作者聊了下,他这样做也是因为担心Activity销毁后还有没处理完的request。

第一次尝试修复

虽然我们上面分析过导致这个内存泄漏的原因似乎不是 因为“Activity销毁后还有没处理完的request”,但是我搜了一下发现却真没有跟 LiveAsyncLayoutInflater 相关的泄漏,业务用法也都一样。。。 没有其他思路,于是也这样改一下试试吧,起码不会有坏处。并且我们还加了一个逻辑: 监听Activity destroy,然后遍历 request 队列,把和其关联的 request 移除。这样的目的是因为有些 request 是用的 Application context 来处理的,只在 runInner 中判断,可能会影响后面 request 处理的及时性。

尝试修复上线后发现跟预期的基本“一致”,可以说是毫无作用 😂😂😂

第三次分析

尝试一下看看线下能不能复现,看看复现的场景是什么。

目前我们线下包在 activity destroy的时候都会去分析泄漏,于是改了下代码,当发现跟当前问题引用链一样的时候就将额外补充的一些信息一起上报上来排查。

然而用这个包测试了一段时间,没复现。。。然后又看了下线下的内存泄漏监控,这个每天也会上报不少的泄漏问题,结果这个问题一个都没有。。。

线上量很大,线下一个都没有,难道是某处逻辑线上包跟线下包不一样触发了这个问题?

回想一开始分析的时候有个判断:如果runInner被内联到run里面,那问题就可以解释,当时反编译已经排除了,但是忽然想到当时包用错了。。。拿的是线下包,线下包都是 fast 模式,是不会走 optimized 的,所以肯定不会内联。相关配置如下:

if (BuildContext.isFast) {// ...proguardFile 'proguard_not_opt.pro'// ...
}// ... proguard_not_opt.pro
-dontoptimize
// ...

因为 optimize 特别耗时,线下包关闭也是很合理的。但是线上包是没有这个配置的,也就是打开了 optimize。另外混淆配置中没有关闭 unique method inline:

# 下面的没有配:
# -optimizations !method/inlining/unique

runInner 也只有一处调用,在打开optimize且没有禁止 unique method inline 时是可能inline的。于是找了个线上包再来看看,果然 内 联 了 !!!

既然内联了,那么 runInner 的本地变量表中的对象就被合到了 run 中,而 run 里面是个 while (true) 死循环,生命周期无限长,所以如果这里面的本地变量表中持有 BasicInflater 那么它就是gc root,并且进一步导致它的 context 泄漏,我们来看下AsyncLayoutInflater的这段代码:

.method public final run()V
.registers 6
.prologue
:catch_0
:goto_0
:try_start_0
iget-object v0, p0, LX/pJX;->LIZIZ:Ljava/util/concurrent/ArrayBlockingQueue;
invoke-virtual {v0}, Ljava/util/concurrent/ArrayBlockingQueue;->take()Ljava/lang/Object;
move-result-object v4
check-cast v4, LX/pJZ;
const/4 v3, 0x0
:try_end_9
.catch Ljava/lang/InterruptedException; {:try_start_0 .. :try_end_9} :catch_0
:try_start_9
iget-object v0, v4, LX/pJZ;->LIZ:LX/pJW;
iget-object v2, v0, LX/pJW;->LIZ:Landroid/view/LayoutInflater;
iget v1, v4, LX/pJZ;->LIZJ:I
iget-object v0, v4, LX/pJZ;->LIZIZ:Landroid/view/ViewGroup;
invoke-virtual {v2, v1, v0, v3}, Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View;
move-result-object v0
iput-object v0, v4, LX/pJZ;->LIZLLL:Landroid/view/View;
:try_end_17
.catch Ljava/lang/RuntimeException; {:try_start_9 .. :try_end_17} :catch_17
:catch_17
iget-object v0, v4, LX/pJZ;->LIZ:LX/pJW;
iget-object v0, v0, LX/pJW;->LIZIZ:Landroid/os/Handler;
invoke-static {v0, v3, v4}, Landroid/os/Message;->obtain(Landroid/os/Handler;ILjava/lang/Object;)Landroid/os/Message;
move-result-object v0
invoke-virtual {v0}, Landroid/os/Message;->sendToTarget()V
goto :goto_0
.end method
  1. iget-object v2, v0, LX/pJW;->LIZ:Landroid/view/LayoutInflater; 知道 BasicInflater 被赋值到了 v2寄存器
  2. invoke-virtual {v2, v1, v0, v3}, Landroid/view/LayoutInflater;->inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View; 通过 v2中的BasicInflater去inflate 布局
  3. v2寄存器没有复用,当通过 goto :goto_0 进行下一次循环,并且取出下一个 request,v2 被赋予下一个 BasicInflater 实例引用之前,v2 一直持有者上一个 BasicInflater 引用,而这个就是导致泄漏的引用。 当前处于while循环中,等待下一个request,跟我们上面分析的“导致泄漏的BasicInflater处于空闲状态一致”,并且长时间处于这个状态,所以抓到的概率就很大了。

为什么AsyncLayoutInflater$BasicInflater 的实例数始终比 AsyncLayoutInflater多一个呢?,原因是:AsyncLayoutInflater 的引用是存在 v0 寄存器中的,而v0寄存器被多次复用,所以AsyncLayoutInflater的引用并没有被一直持有。

到此问题基本就分析清楚了,但是还有一个遗留问题,上面提到的业务中copy出来的 LiveAsyncLayoutInflater为什么没有泄漏呢?原因是:

  1. 他的 BasicInflater 引用也是放到 v2寄存器中的
  2. 但是这个类中的方法有插桩,runInner被内联之后,插桩代码也被内联了,在进行while(true)的下一轮循环时,首先会去执行插桩代码,而插桩代码复用了v2寄存器,因此就不再持有BasicInflater的引用了,因此没有泄漏

所以这块代码没有导致泄漏,其实是有点运气在其中的。寄存器的分配本身比较复杂,而且D8在寄存器分配上也不是非常完善的,几年前在西瓜也曾遇到一个D8寄存器分配导致的一个crash问题:D8编译器“bug”导致简单代码crash

修复

问题已经明确,修复就比较简单了,只要让 runInner 不内联就行了。而之所以它会被内联,是因为 proguard/R8 有个优化,如果某个方法只有一处调用(当然还要满足很多其他条件),那么就将它内联,并删除原方法。因此我们改一下,找一个其他地方调一下就可以规避,比如:

if (/* 此处返回 false,让if block不执行就行 */) {  runInner();// Make sure never reach here  
}

这样静态分析不是一处调用,不会内联,实际上也不会走到,也不影响逻辑。

不过线上我没有这样改,因为还有其他办法可以不用改代码:给 runInner keep 一下就也不会内联了,原因也好理解:如果方法被keep了,那么原方法不能删,而这个又不是个小方法,要是内联的话,字节码变大了,方法数也没少,必然负向了,那还内联干啥。

-keepclassmembers class androidx.asynclayoutinflater.view.AsyncLayoutInflater$InflateThread {  public void runInner();  
}

改了之后,泄漏解决了~😊

顺便提一句

runInner 里面判断context(activity)是否destroy,如果destroy的话,就拦截不处理这个 request还有个小麻烦:onInflateFinished 这个回调是否要触发?

public interface OnInflateFinishedListener {void onInflateFinished(@NonNull View view, @LayoutRes int resid,@Nullable ViewGroup parent);
}
  1. 如果要回调onInflateFinished,那么 view 如何获取?null 肯定不行,因为原本接口中有 @NonNull,导致很多业务代码不会判空
  2. 不回调也不行,因为有些业务有个“优化”逻辑,如果上一个 inflate 没有回来,后续就走同步 inflate,所以如果不回调,相当于关闭了异步 inflate 功能。。。

所以当时我们加了个 onCancel 回调,业务可以在这里处理被拦截的情况:

public interface OnInflateFinishedListener {void onInflateFinished(@NonNull View view, @LayoutRes int resid,@Nullable ViewGroup parent);/*** if context (activity) destroyed, InflateRequest will be cancel, and this method will be invoked.** It can be invoked on different thread* @param resid*/default void onCancel(@LayoutRes int resid) {}
}

比如:

override fun onCancel(resid: Int) {isAsyncInflating = false
}

但是这样也不优雅,而且也不是所有业务都知道有这么个 onCancel api,如果改成非default接口,又要改动很多地方的代码。

好在上面也看到了这个泄漏并非因为 “Activity destroy后还有没处理完的 InflateRequest,可能导致短暂泄漏”,拦截的必要性也不大。

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

相关文章:

编译内联导致内存泄漏的问题定位修复

作者:0x264 问题 线上长时间存在一个跟异步 inflate 相关的量级较大的内存泄漏,如下所示: 第一次分析 从内存泄漏粗略看有几个信息: 被泄漏的Activity有很多,所以可能跟某个具体业务的关系不大引用链特别短&#xf…...

基于WebSocket实现的后台服务

基于WebSocket实现的后台服务,用于接收客户端的心跳消息,并根据心跳消息来维护客户端连接。 具体实现中,服务启动后会创建一个HttpListener对象,用于监听客户端的WebSocket连接请求。当客户端连接成功后,服务会为每个…...

Go语言中的结构体详解

关于 Golang 结构体 Golang 中没有“类”的概念,Golang 中的结构体和其他语言中的类有点相似。和其他面向对 象语言中的类相比,Golang 中的结构体具有更高的扩展性和灵活性。 Golang 中的基础数据类型可以表示一些事物的基本属性,但是当我们…...

pytest自动化测试指定执行测试用例

1、在控制台执行 打开cmd,进入项目目录 指定执行某个模块 pytest testcases\Logistics\Platform\CarSource\test_CarSourceList.py 指定执行某个目录及其子目录的所有测试文件 pytest testcases\Logistics\Platform\CarSource 指定执行某个模块的某个类的某个测试用例 pyte…...

英伟达 H100 vs. 苹果M2,大模型训练,哪款性价比更高?

M1芯片 | Uitra | AMD | A100 M2芯片 | ARM | A800 | H100 关键词:M2芯片;Ultra;M1芯片;UltraFusion;ULTRAMAN;RTX4090、A800;A100;H100;LLAMA、LM、AIGC、CHATGLM、LLVM、LLM、LLM…...

var、let和const的区别

先简单了解一下 var声明的变量会挂载在window上,而let和const声明的变量不会: var a 100; console.log(a,window.a); // 100 100let b 10; console.log(b,window.b); // 10 undefinedconst c 1; console.log(c,window.c); // 1 undefined v…...

(css)AI智能问答页面布局

(css)AI智能问答页面布局 效果&#xff1a; html <!-- AI框 --><div class"chat-top"><div class"chat-main" ref"chatList"><div v-if"!chatList.length" class"no-message"><span>欢迎使…...

【Pytorch学习】pytorch中的isinstance() 函数

描述 isinstance() 函数来判断一个对象是否是一个已知的类型&#xff0c;类似 type()。 isinstance() 与 type() 区别&#xff1a; type() 不会认为子类是一种父类类型&#xff0c;不考虑继承关系。 isinstance() 会认为子类是一种父类类型&#xff0c;考虑继承关系。 如果要判…...

(树) 剑指 Offer 07. 重建二叉树 ——【Leetcode每日一题】

❓剑指 Offer 07. 重建二叉树 难度&#xff1a;中等 输入某二叉树的 前序遍历 和 中序遍历 的结果&#xff0c;请构建该二叉树并返回其根节点。 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 示例 1: Input: preorder [3,9,20,15,7], inorder [9,3,15,20,7] …...

Gitlab 合并分支与请求合并

合并分支 方式一&#xff1a;图形界面 使用 GitGUI&#xff0c;右键菜单“GitExt Browse” - 菜单“命令” - 合并分支 方式二&#xff1a;命令行 在项目根目录下打开控制台&#xff0c;注意是本地 dev 与远程 master 的合并 // 1.查看本地分支&#xff0c;确认当前分支是否…...

【Matter】基于Ubuntu 22.04 编译chip-tool工具

前言 编译过程有点曲折&#xff0c;做下记录&#xff0c;过程中&#xff0c;有参考别人写的博客&#xff0c;也看github 官方介绍&#xff0c;终于跑通了~ 环境说明&#xff1a; 首先需要稳定的梯子&#xff0c;可以访问“外网”ubuntu 环境&#xff0c;最终成功实验在Ubunt…...

将 MongoDB 的 List<Document> 转换为对象列表

当我们使用 MongoDB 存储数据时&#xff0c;经常会涉及到将 MongoDB 的文档对象转换为对象列表的需求。在 Java 中&#xff0c;我们可以使用 MongoDB 的 Java 驱动程序和自定义类来实现这一转换过程。 本篇博客将介绍如何将 MongoDB 中的 List<Document> 转换为对象列表。…...

【Linux下6818开发板(ARM)】SecureCRT串口和交叉编译工具(巨细版!)

(꒪ꇴ꒪ ),hello我是祐言博客主页&#xff1a;C语言基础,Linux基础,软件配置领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff01;送给读者的一句鸡汤&#x1f914;&#xff1a;集中起来的意志可以击穿顽石!作者水平很有限&#xff0c;如果发现错误&#x…...

应届生如何快速找Java开发工程师,先学会这17个基础问题

一、Java 基础 JDK 和 JRE 有什么区别&#xff1f; JDK&#xff1a;Java Development Kit 的简称&#xff0c;java 开发工具包&#xff0c;提供了 java 的开发环境和运行环境。 JRE&#xff1a;Java Runtime Environment 的简称&#xff0c;java 运行环境&#xff0c;为 java 的…...

数学建模学习(5):数学建模各类题型及解题方案

一、数学建模常见的题型 总体来说&#xff0c;数学建模赛题类型主要分为&#xff1a;评价类、预测类和优化类三种&#xff0c;其中优化类是最常见的赛题类 型&#xff0c;几乎每年的地区赛或国赛美赛等均有出题&#xff0c;必须要掌握并且熟悉。 二、评价类赛题 综合评价是数学…...

【学习笔记】视频检测方法调研

目录 1 引言2 方法2.1 视频目标跟踪2.1.1 生成式模型方法2.1.2 判别式模型方法2.1.2.1 基于相关滤波跟踪2.1.2.2 基于深度学习跟踪 2.2 视频异常检测2.2.1 基于重构方法2.2.2 基于预测方法2.2.3 基于分类方法2.2.4 基于回归方法 2.3 深度伪造人脸视频检测2.3.1 基于RNN时空融合…...

idea terminal npm指令无效

文章目录 一、修改setting二、修改启动方式 一、修改setting 菜单栏&#xff1a;File->Settings 二、修改启动方式 快捷方式->右键属性->兼容性->勾选管理员身份运行...

低代码开发平台源码

什么是低代码开发平台&#xff1f; 低代码来源于英文“Low Code&#xff0c;它意指一种快速开发的方式&#xff0c;使用最少的代码、以最快的速度来交付应用程序。通俗的来说&#xff0c;就是所需代码数量低&#xff0c;开发人员门槛低&#xff0c;操作难度低。一般采用简单的图…...

【UE5 多人联机教程】04-加入游戏

效果 步骤 1. 新建一个控件蓝图&#xff0c;父类为“USC_Button_Standard” 控件蓝图命名为“UMG_Item_Room”&#xff0c;用于表示每一个搜索到的房间的界面 打开“UMG_Item_Room”&#xff0c;在图表中新建一个变量&#xff0c;命名为“Session” 变量类型为“蓝图会话结果…...

自然语言处理从入门到应用——LangChain:模型(Models)-[大型语言模型(LLMs):缓存LLM的调用结果]

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 from langchain.llms import OpenAI在内存中缓存 import langchain from langchain.cache import InMemoryCachelangchain.llm_cache InMemoryCache()# To make the caching really obvious, lets use a slower mode…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

C++中string流知识详解和示例

一、概览与类体系 C 提供三种基于内存字符串的流&#xff0c;定义在 <sstream> 中&#xff1a; std::istringstream&#xff1a;输入流&#xff0c;从已有字符串中读取并解析。std::ostringstream&#xff1a;输出流&#xff0c;向内部缓冲区写入内容&#xff0c;最终取…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障

关键领域软件测试的"安全密码"&#xff1a;Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力&#xff0c;从金融交易到交通管控&#xff0c;这些关乎国计民生的关键领域…...