android 如何分析应用的内存(十八)终章——使用Perfetto查看内存与调用栈之间的泄露

news2024/11/25 4:57:07

android 如何分析应用的内存(十八)

在前面两篇文章中,先是介绍了如何用AS查看Android的堆内存,然后介绍了使用MAT查看
Android的堆内存。AS能够满足基本的内存分析需求,但是无法进行多个堆的综合比较,因此引入了MAT工具。它可以很好的在两个堆之间进行比较。两个工具已经能解决95%的内存问题了。

但是在一些极端情况下,如多线程带来的内存泄漏,上面两个工具可能就不太好定位问题,即泄漏点的调用栈和调用线程了。

对于Android来讲,怎样才能定位这种多线程调用带来的内存呢?下面是一些经验之谈:

  1. 如果能够添加代码,对于不同的线程,在泄露的对象上,添加一个字段,用于表示线程的id。此方法比较简单,就不再赘述
  2. 如果不能添加代码,那么就需要同时录制java的调用栈和java的堆。根据在同时间段,进行逻辑比较,得出是哪一个调用栈导致的内存泄露。如,通过比较哪个调用栈调用的次数最多,哪个调用栈分配的内存最多。甚至需要在一段时间间隔之间做差分,来获得泄露的对象和调用栈之间的关系。这些方法往往需要一定的经验进行逻辑处理。

本文将围绕不能添加代码的情况,进行分析这种极端情况。

Android Studio虽然提供了java调用栈的录制和java 堆的转储。但是他们无法同时使用,导致在时间轴上面的对比无法完成。但是Perfetto提供了类似的功能。

接下来将以Perfetto为工具,首先介绍同时录制java的调用栈和java堆,在逻辑上进行比较,得出泄漏点的调用栈。然后在一定时间间隔上,对java调用栈和java堆进行差分比较,得出泄漏点的调用栈。

Perfetto同时录制堆栈和heap dump

在android 如何分析应用的内存(十三)——perfetto一文中我们介绍了Perfetto的使用方法。接下来我们将使用常规模式,来同时录制java heap和java callstack。

adb shell perfetto \
  -c - --txt \
  -o /data/misc/perfetto-traces/trace \
<<EOF

buffers: {
    size_kb: 63488
    fill_policy: DISCARD
}
buffers: {
    size_kb: 2048
    fill_policy: DISCARD
}
data_sources: {
    config {
        name: "android.packages_list"
        target_buffer: 1
    }
}
data_sources: {
    config {
        name: "android.heapprofd"
        target_buffer: 0
        heapprofd_config {
            sampling_interval_bytes: 4096
            process_cmdline: "com.example.test_malloc"
            shmem_size_bytes: 8388608
            block_client: true
            ## 只录制com.android.art的堆
            heaps: "com.android.art"
        }
    }
}
## 增加了第二个数据源
data_sources: {
    ## 数据源配置
    config {
        ## 名字必须为"android.java_hprof"
        name: "android.java_hprof"
        ## 指定目标buffer,关于目标buffer的含义见android 如何分析应用的内存(十三)
        target_buffer: 0
        ## java_hprof的配置
        java_hprof_config {
            ## dump的进程名为:"com.example.test_malloc"
            process_cmdline: "com.example.test_malloc"
        }
    }
}
## 时间修改为60s
duration_ms: 60000

EOF

在上面的命令中,我们新增了一个data_source,并且将其指定为录制java heap。同时还有另外一个data_source即android.heapprofd。它会录制指定进程的堆内存,因为我们暂时不需要native堆,所以在heaps中设置了“com.android.art”

关于Perfetto配置文件的说明见:android 如何分析应用的内存(十三)——perfetto

分析结果

输入上面的命令,然后操作APP,60s之后,会在/data/misc/perfetto-traces/trace中形成结果文件,将其pull出来,用https://ui.perfetto.dev/打开即可。如下图
在这里插入图片描述

图中:

  • 标记1:到GC root的这条路的retained size大小。
  • 标记2:到GC root的这条路径的Retained set。

注意:某个对象的Retained size可以理解为:回收这个对象之后,会回收Retained size这么多内存。某个对象的Retained set可以理解为:回收这个对象之后,被它引用且能被回收的对象的集合。某个路径上的Retained size,即为这个路径上的对象的Retained size之和。某个路径上的Retained set,即为这个路径上的对象的Retained set之和

Retained size的计算见:<android 如何分析应用的内存(十六)——使用AS查看Android堆>

注意注意:在上面的操作中,我故意放置了一个小小的漏洞。仔细观察图中,两个菱形的位置,一个在开头,一个在结尾。为了进行泄露点的逻辑分析dump heap和callstack,这两个棱形应该越靠近越好,因此,对上面的配置,调整如下:

adb shell perfetto \
  -c - --txt \
  -o /data/misc/perfetto-traces/trace \
<<EOF

buffers: {
    ## 将buffer增大1000倍,否则出现Perfetto ui解析出错
    size_kb: 63488000
    fill_policy: DISCARD
}
buffers: {
    size_kb: 2048
    fill_policy: DISCARD
}
data_sources: {
    config {
        name: "android.packages_list"
        target_buffer: 1
    }
}
data_sources: {
    config {
        name: "android.heapprofd"
        target_buffer: 0
        heapprofd_config {
            sampling_interval_bytes: 4096
            process_cmdline: "com.example.test_malloc"
            shmem_size_bytes: 8388608
            heaps: "com.android.art"
            continuous_dump_config {
                ## 10s之后,才开始第一次dump
                dump_phase_ms: 10000
                ## 每隔2s,dump一次
                dump_interval_ms: 10000
            }
        }
    }
}

data_sources: {
    config {
        name: "android.java_hprof"
        target_buffer: 0
        java_hprof_config {
            process_cmdline: "com.example.test_malloc"
            continuous_dump_config {
                ## 10s后,才开始第一次dump
                dump_phase_ms: 10000
                ## 每隔2s,dump一次
                dump_interval_ms: 10000
            }
        }
    }
}
## 总时间变成 30s
duration_ms: 30000

EOF

注意:这里dump heap时,需要先启动APP,再运行Perfetto。

得到的结果如下图:
在这里插入图片描述

调整时间轴,将两个重合的棱形,放大一点如下图:
在这里插入图片描述

图中,点击第二个棱形图标,出现从GC root的火焰图。

晴天霹雳!!!发现Perfetto在我的Pixel 3上面拉取出来的java heap dump并没有正确的计算引用链。导致我的火焰图,没有正确的反应内存泄露。经过深入分析,发现问题出现在Classloader和它加载的对象之间的引用链没有正确处理,导致了一些从GC root可达的对象,变成了不可达,即已经是泄露的对象,变成了没有泄露。

我们的目标是为了同时采集callstack和heap dump进行逻辑分析。因此我们可以忽略这种影响,直接操作数据库即可。

Heap dump的数据库表

java heap dump只会涉及到3张表:

  • heap_graph_reference:存储引用
  • heap_graph_object:存储对象
  • heap_graph_class:存储类

为了能够直观的展示这些表的结构。下面使用工具,将trace文件的数据库导出来,然后使用数据库UI工具进行查看

导出数据库

使用如下的命令进行数据库的导出。

./trace_processor /Users/biaowan/Documents/trace_single_conti -e  ~/Documents/trace_to_sqlite.db

本次实验,使用了DBeaver的社区版,进行数据库的查看。打开导出的数据库:trace_to_sqlite.db.如下图:

在这里插入图片描述

表说明

在真正使用之前,需要对表的各个列做一个说明:

heap_graph_reference

  • id 本引用唯一的id
  • type 本表名字,即heap_graph_reference
  • reference_set_id 引用对象集ID,如果这个引用是在某个对象中,那么在heap_graph_object中的reference_set_id和此值相等
  • owned_id 被引用的对象的id,即heap_graph_object的id
  • owner_id 使用这个引用的对象的id
  • field_name 这个引用的字段名
  • field_type_name 这个引用的字段的类型名
  • deobfuscated_field_name 反混淆之后的字段名

heap_graph_object

  • id 本对象的id
  • type 本表的名字
  • upid pid
  • graph_sample_ts 采样时间,即dump这个对象的时间
  • self_size 自身大小
  • native_size native大小
  • reference_set_id 本对象引用的其他对象的应用集id
  • reachable 从根对象是否可达,如果可达,则不可回收,否则,可回收(有bug)
  • type_id 本对象对应的class的id
  • root_type 如果不为空,则说明是根对象

heap_graph_class

  • id 本class的id
  • type 本表名字
  • name 本class的名字
  • deobfuscated_name 本class反混淆之后的名字
  • location 本class在什么地方
  • classloader_id classloader的id,这个id即为heap_graph_object的id
  • superclass_id 父类id,对应于本表的id
  • kind 类型

有了这些表的使用说明之后,我们就可以根据自己的需要使用SQL查询语句查看堆中的对象。

接下来,我们先简单的查看,在第二个heap dump中,哪些对象占据的内存空间最大。

查看第二个堆中的内存占用情况

注意:为什么要使用第二个堆?因为在我们的采集数据中,是从Perfetto开始运行的第十秒开始采集,然后再过十秒再次采集。而第一次采集的数据,因为callstack和heap时间间隔较大,所以不采用它。

选取正确的时间戳

使用SQL语句如下:

select * from heap_graph_object group by graph_sample_ts;

在这里插入图片描述

从图中,我们可以看到有三个时间段。分别对应于火焰图中的三个菱形。其余的菱形为callstack

我们要分析的就是第二个采样时间即:398831516584184

将对应时间戳的堆中对象,按照类进行分类,并统计其大小,倒序输出

select class.id,sum(object.self_size) as totalSize,class.name 
  from heap_graph_class as class inner join heap_graph_object as object
  on class.id=object.type_id 
  where object.graph_sample_ts=398831516584184
  group by class.id
  order by totalSize desc;

结果如下
在这里插入图片描述

图中可以看到最大的对象类型是int[],其次为String。

注意:本次查询,即包括了能被回收的对象,也包括了不能回收的对象。因为Perfetto本身的错误,不能通过reachable字段来判断是否可回收。事实上,可以自己写一个脚本,递归处理classloader的引用关系,然后修改数据库中的reachable字段。不过我们的任务是为了找到泄漏对象和调用栈的关系。泄露对象其实已经明确了。即可以通过前面两篇的文章,来确定泄露的对象。见:

  • android 如何分析应用的内存(十七)——使用MAT查看Android堆:http://t.csdn.cn/c3BfM
  • android 如何分析应用的内存(十六)——使用AS查看Android堆:http://t.csdn.cn/xYGoA

如果仅仅是上面的查询结果,并不能简单的归因于内存泄露对象为int[],好在我们有AS和MAT工具可以辅助归因。随着Perfetto工具的完善(修复reachable字段的值),仅用Perfetto也可以很好的找到内存泄露点。

又因为前两篇文章和本篇文章,都是使用的一个测试APP,所以,我们已经将内存泄漏点归因于int[]了。接下来就是将这个内存泄漏点与调用栈联系起来

将泄露对象与调用栈简单的联系起来

上面小节说明了泄露对象为int[],如果同一时间在调用栈中某个调用点执行的次数最多,或者在该调用点分配的对象最大,即可简单的将其进行逻辑联系起来。认为是该调用点导致的内存泄露。

我们点击离第二个堆最近的,棱形图标,如下图:
在这里插入图片描述

因为int[]占用了大量的空间,所以我们选择调用栈的total allocation size.如下
在这里插入图片描述

从图中,我们可以看到,doText()这个调用点,几乎占据了99%的分配大小。毫无疑问,int[]的泄露是这个doText()调用点导致的。但是这里面有两个doText(),分别占据约60%和约40%,可以肯定这两个调用点,都导致了内存的泄露。

然后查看其火焰图,可以找到整个调用栈和调用线程。

思考:一切看起来很简单,对吗?是否思考过一个问题——他们的时间点真的对的上吗?或者他们的时间点真的合理吗?

事实上:java的heap dump的数据,是从程序开始运行到dump点的堆中数据。而heapprofd中的数据(即这里的java调用栈)为Perfetto开始运行,到录制点之间的数据。画个图如下
在这里插入图片描述

解决办法:可能读者会想到将Perfetto在app启动之前启动,这样他们开始计算的时间点都是从app启动的时候开始了。然而,根据实测,要抓取java heap,必须先app启动,所以此种方法不可取。真正要解决这种问题,我们只需要再次进行一次录制,然后分别对callstack和heap进行差分比较即可。

用差分比较解决解决剩下难以定位的问题

用差分比较,可以排除,上述时间起始点不同步带来的干扰,同时还能排除,线程过多调用栈过杂带来的干扰。接下来看看使用步骤

重新录制更长时间段的内存数据和调用栈

将上面的配置文件的总时长更改为60s。然后重新录制,得如下图所示的情况

在这里插入图片描述

如上图我们采样了大约6组数据,现在我们选择第40s和第50s的两组进行差分比较分析。当然也可以选择其他组进行比较。

差分分析两个堆中查看增加内容最多的数据

  1. 先找出两者之间的时间。如下
select * from heap_graph_object group by graph_sample_ts;

在这里插入图片描述

根据结果我们选择上图的两个时间,分别为:462051768285433和462061780815388

  1. 将两个时间上的堆做减法,留下40s到50s中增加的对象。
    为了对两个表有一个感性的认知,可以执行下面的指令,进行查看
select class.id,object.graph_sample_ts,
    sum(object.self_size) as totalSize,class.name 
  from heap_graph_class as class inner join heap_graph_object as object
  on class.id=object.type_id 
  where object.graph_sample_ts=462051768285433 or 
     object.graph_sample_ts=462061780815388
  group by class.id
  order by totalSize desc;

在这里插入图片描述

从图中可以看到,不同时间段上对象的大小之和。

接下来,将50s和40s之间的堆,进行相减,如下:

select t2.totalSize - COALESCE(t1.totalSize,0) as diff,t2.name
from (
   select class.id,object.graph_sample_ts,
      sum(object.self_size) as totalSize,class.name 
   from heap_graph_class as class inner join heap_graph_object as object
   on class.id=object.type_id 
   where object.graph_sample_ts=462061780815388
   group by class.id,object.graph_sample_ts
   order by totalSize desc
) as t2 left join (
   select class.id,object.graph_sample_ts,
      sum(object.self_size) as totalSize,class.name 
   from heap_graph_class as class inner join heap_graph_object as object
   on class.id=object.type_id 
   where object.graph_sample_ts=462051768285433
   group by class.id,object.graph_sample_ts
   order by totalSize desc
) as t1 on t2.name = t1.name
order by diff desc

在这里插入图片描述

为了更好的计算它们所占的百分比,我在这里求总和之后,直接写入插入语句中,如下:

select (t2.totalSize - COALESCE(t1.totalSize,0))/2119766.0 
   as percentage,t2.totalSize - COALESCE(t1.totalSize,0) as diff,t2.name
from (
   select class.id,object.graph_sample_ts,
      sum(object.self_size) as totalSize,class.name 
   from heap_graph_class as class inner join heap_graph_object as object
   on class.id=object.type_id 
   where object.graph_sample_ts=462061780815388
   group by class.id,object.graph_sample_ts
   order by totalSize desc
) as t2 left join (
   select class.id,object.graph_sample_ts,
      sum(object.self_size) as totalSize,class.name 
   from heap_graph_class as class inner join heap_graph_object as object
   on class.id=object.type_id 
   where object.graph_sample_ts=462051768285433
   group by class.id,object.graph_sample_ts
   order by totalSize desc
) as t1 on t2.name = t1.name
order by percentage desc

如下图
在这里插入图片描述

从图中我们看到了97%的对象为int[],接下来只要比较40s到50s之间,什么样的调用点被调用的次数最多,或者该调用点分配的内存最多,那么这个调用点大概率就是产生这97%的int[]的地方

差分分析同时间段的调用栈

在介绍如何查看调用栈的差分之前,我们需要知道跟调用栈相关的数据库中的表,分别有下面三张表:

  • heap_profile_allocation:存储分配
  • stack_profile_frame:存储栈帧名
  • stack_profile_callsite:存储调用点

当然除了上面三个表以外,还有其他的表,但是对于我们的分析关系不大,故不在啰嗦,查看所有表的信息可参阅:https://perfetto.dev/docs/analysis/sql-tables

调用栈表说明

heap_profile_allocation

  • id 唯一id
  • type 本表名
  • ts 采样时间
  • upid pid
  • heap_name 堆名字
  • callsite_id 调用点id,即stack_profile_callsite的di
  • count 分配的次数,正数就是该调用点的分配次数,负数就是该调用点的释放次数
  • size 分配的大小,同样有正负之分,正数表示分配大小,负数表示释放大小

stack_profile_frame

  • id 唯一id
  • type 本表名
  • name 函数名
  • mapping 该函数映射到哪一个库,如so,.dex 即stack_profile_mapping的id
  • rel_pc 相对于映射库的pc值
  • symbol_set_id 该函数名对应的符号表的id,即stack_profile_symbol的id
  • deobfuscated_name 反混淆之后的名字

stack_profile_callsite

  • id 唯一id
  • type 本表名
  • depth 到调用栈顶部的距离,多一个函数,则深度加一
  • parent_id 本调用点的父函数的调用点id。即stack_profile_callsite的id
  • frame_id 帧id,即stack_profile_frame的id

查看各个采样时间

使用如下的命令查看。

select *,sum(count) as totalCount ,sum (size) as totalSize 
from heap_profile_allocation group by ts;

在这里插入图片描述

从图中可以看到,整个数据被分成了6个时间段,刚好对应于火焰图的六菱形。在火焰图中,每个棱形表示的是从开始抓取到棱形位置对应时间的所有分配。

然而数据库中的每个时间点,表示的是从上一个时间点到本次抓取的所有数据,因此查看40s到50s之间的数据,只需要看第50s的数据即可。也就是倒数第二行。

查看40s和50s的调用详细情况

为了便于理解,下面的语句,将40s和50s的两个堆都列出来分析。如下

select * 
from (
   select * 
   from heap_profile_allocation 
   where ts = 462061299971174 
   order by count desc
) as t1
union all 
select * 
from (
   select * 
   from heap_profile_allocation 
   where ts = 462071449972185 
   order by count desc
) as t2

在这里插入图片描述

上图列出了30-40区间的情况,以及40-50区间的情况,稍微计算下各个调用点分配的内存占比,可以知道:701调用点的内存分配占据了40s到50s所有分配的82% 因此我们可以大胆的下结论——40s到50s之间的内存泄露由701分配点导致。

查看701分配点的调用栈

使用下面的递归语句,查看整个701的调用栈,如下

WITH RECURSIVE RecursiveCTE AS (
    SELECT id, parent_id,frame_id
    FROM stack_profile_callsite
    WHERE id = 701
    UNION ALL
    SELECT origin.id, origin.parent_id,origin.frame_id
    FROM stack_profile_callsite origin
    JOIN RecursiveCTE r ON r.parent_id = origin.id
)
SELECT result.id,result.parent_id,frame.name 
FROM RecursiveCTE as result inner join stack_profile_frame as frame 
   on frame.id=result.frame_id 
order by result.id desc;

如下图
在这里插入图片描述

直接观察即可看到40s至50s之间泄露对象int[]由上图的调用栈产生泄露。

注意,使用同样的分析方法,查看调用点518,依然会得出相同的结论,不过他们发生在30s至40s之间。同样的操作步骤,不再继续举例

至此,使用perfetto进行内存分析,已经介绍完毕。

内存方法大总结

万事大吉,关于Android的内存分析已经介绍完毕。现在对前面的所有文章进行一个总结:

native篇

  1. 第零个工具xdd:只能查看任意内存
  2. 第一个工具gdb:它可以查看:寄存器,和任意位置的内存,分析coredump,能查看栈情况,不能查看堆情况
  3. 第二个工具lldb:它可以查看:寄存器,和任意位置的内存,分析coredump,能查看栈情况,不能查看堆情况
  4. 第三个工具自定义malloc:只能查看堆情况,且查看的范围较小,几乎只有自己编译的代码
  5. 第四个工具malloc hook:能查看所有的堆分配情况
  6. 第五个工具malloc统计和libmemunreachable:可以查看所有堆分配情况
  7. 第六个工具malloc debug和libc回调:能查看所有堆分配情况
  8. 第七个工具ASan/HWASan:只能查看linux的堆分配情况,无法查找android的分配情况,列在此处只是为了知识的完整性
  9. 第八个工具perfetto:只能查看堆内存分配情况

java篇

  1. 第零个工具jdb:查看堆帧,本地变量,锁,对象
  2. 第一个工具java debugger for vscode:查看堆栈,本地变量,对象
  3. 第二个工具Android studio:查看堆,对象引用,Retained size,调用栈
  4. 第三个工具MAT:查看堆,对象引用,Retained size ,还能进行堆间差分分析
  5. 第四个工具Perfetto:查看堆,对象引用,Retained size,调用栈,还能在堆和调用栈之间进行差分分析

本系列完。

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

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

相关文章

OptaPlanner笔记1

1.1 什么是OptaPlanner 每个组织都面临规划问题&#xff1a;为产品或服务提供有限的受约束的资源&#xff08;员工、资产、时间和金钱&#xff09;。OptaPlanner用来优化这种规划&#xff0c;以实现用更少的资源来做更多的业务。 这被称为Constraint Satisfaction Programming…

使用maven打包时如何跳过test,有三种方式

方式一 针对spring项目&#xff1a; <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <skipTests>true</skipTests> </configuration> …

Vim学习(三)—— Git Repo Gerrit

Git、Gerrit、Repo三者的概念及使用 三者各自作用&#xff1a; git&#xff1a;版本管理库&#xff0c;在git库中没有中心服务器的概念&#xff0c;真正的分布式。 repo&#xff1a;repo就是多个git库的管理工具。如果是多个git库同时管理&#xff0c;可以使用repo。当然使用…

探讨uniapp的navigator 页面跳转问题

navigator 页面跳转。该组件类似HTML中的<a>组件&#xff0c;但只能跳转本地页面。目标页面必须在pages.json中注册。 "tabBar": {"color": "#7A7E83","selectedColor": "#3cc51f","borderStyle": "bl…

五、Netty高性能架构设计

目录 5.1 线程模型基本介绍5.2 传统阻塞I/O服务模型5.2.1 工作原理5.2.2 阻塞IO模型特点5.2.3 阻塞IO存在的问题 5.3 Reactor模式5.3.1 针对传统阻塞IO服务模型的2个缺点&#xff0c;解决方案5.3.2 IO复用 线程池&#xff0c;就是Reactor模式设计的基本思想 5.1 线程模型基本介…

蓝桥杯-统计子矩阵

统计子矩阵 题目链接 思路&#xff1a; 使用前缀和滑动窗口 &#xff0c;可以先计算出纵向或横向的前缀和&#xff0c;matrix[i][j]表示前i行第j列之和 然后遍历上边界top和下边界buttom&#xff0c;再这个上下边界内使用滑动窗口&#xff0c;由于前面维护了纵向前缀和&…

榜单!全年或超150万辆!行泊一体系统方案供应商TOP10出炉

作为域控集中架构下的产物&#xff0c;智能驾驶赛道的行泊一体方案正在成为市场的主流配置&#xff0c;同时&#xff0c;各类计算&#xff08;芯片&#xff09;方案也都在发力这个细分赛道。 高工智能汽车研究院认为&#xff0c;和NOA不同&#xff0c;作为高低速组合功能的行泊…

CAD随机粗糙度表面插件

插件介绍 CAD随机粗糙度表面插件可用于在AutoCAD软件内生成随机高度分布的表面三维实体模型&#xff0c;适用于科研论文绘图、有限元建模、随机地形模拟等方面的应用。 插件可指定的参数有三维模型的长、宽、高&#xff1b;随机粗糙度表面信息中网格尺寸控制模型生成的精细程…

【LeetCode-简单】剑指 Offer 29. 顺时针打印矩阵(详解)

题目 输入一个矩阵&#xff0c;按照从外向里以顺时针的顺序依次打印出每一个数字。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9]] 输出&#xff1a;[1,2,3,6,9,8,7,4,5]示例 2&#xff1a; 输入&#xff1a;matrix [[1,2,3,4],[5,6,7,8],[9,10,1…

根据源码,模拟实现 RabbitMQ - 从需求分析到实现核心类(1)

目录 一、需求分析 1.1、对 Message Queue 的认识 1.2、消息队列核心概念 1.3、Broker Server 内部关键概念 1.4、Broker Server 核心 API &#xff08;重点实现&#xff09; 1.5、交换机类型 Direct 直接交换机 Fanout 扇出交换机 Topic 主题交换机 1.6、持久化 1.7…

美团软件测试工程师高频面试题和答案

前言 8月底了&#xff0c;马上到了大家的找工作的高峰期了&#xff01;为了帮助大家更好的备战面试和跳槽&#xff0c;可以在众多求职者中脱颖而出&#xff0c;我帮大家准备了丰富的企业真实面试题&#xff0c;大家赶紧收藏吧&#xff01; 1、说下你最近做的项目&#xff0c;你…

C语言学习系列-->看淡指针(2)

文章目录 前言一、数组名的理解二、使用指针访问数组三、一维数组传参本质四、二级指针五、指针数组六、指针数组模拟二维数组 前言 不把指针学的扎实&#xff0c;可不敢说自己C语言基础学的好 一、数组名的理解 #include <stdio.h> int main() {int arr[10] { 1,2,3,4…

利用python实现批量登录网络设备进行日常巡检

利用python实现批量登录网络设备 实现ensp与物理机互通ensp 配置配置网络设备远程登录 用python实现批量登录常见问题 通过阅读本文可以学习自动化运维相关知识&#xff0c;本文章代码可以直接使用&#xff0c;通过批量登录功能后&#xff0c;可以按照自己意愿进行功能更改与完…

电路基础笔记(更新中)

导体 导体是指那些能够轻易传递电荷&#xff08;电子&#xff09;的物质。在固体、液体或气体中&#xff0c;电子可以在导体内部自由移动&#xff0c;从而形成电流。导体的电导性取决于它的电子结构和能带特性。常见的导体包括金属&#xff08;如铜、铝、铁等&#xff09;以及一…

UNIX网络编程——TCP协议API 基础demo服务器代码

目录 一.TCP客户端API 1.创建套接字 2.connect连接服务器​编辑 3.send发送信息 4.recv接受信息 5.close 二.TCP服务器API 1.socket创建tcp套接字(监听套接字) 2.bind给服务器套接字绑定port,ip地址信息 3.listen监听并创建连接队列 4.accept提取客户端的连接 5.send,r…

怎么做Tik Tok海外娱乐公会呢?新加坡市场怎么样?

一、为什么选择TikTok直播 1. 海外市场潜力巨大 • 自2016年始&#xff0c;多家直播平台陆续拓展至东南亚、中东、俄罗斯、日韩、欧美、拉美等地区。 • 海外市场作为直播发展新蓝海&#xff0c;2021年直播行业整申请cmxyci体规模达百亿美元&#xff0c;并维持高速增长。 &a…

在阿里云服务器上安装部署WDCP主机管理系统教程

阿里云百科分享在阿里云服务器上安装部署WDCP主机管理系统流程&#xff0c;WDCP&#xff08;WDlinux Control Panel&#xff09;是一套Linux服务器及虚拟主机管理系统&#xff0c;通过Web控制和管理服务器。在WDCP的后台中&#xff0c;您可以更方便地使用Linux系统作为网站服务…

STM32CubeMX之freeRTOS消息队列

创建一个消息队列&#xff0c;两个发送任务&#xff0c;一个接受任务 发送任务一&#xff1a;等待时间为0 发送任务二&#xff1a;等待时间为最大 接受为0 简单来说就是&#xff1a; 任务一&#xff1a;一个普写 一个死写 一个普读 任务二&#xff1a;创造队列 一个普写 …

JDBC快速入门操作

一、jdbc简介 JDBC是java用于连接数据库的api&#xff0c;数据库软件有多种&#xff0c;像MySQL,SQLsever&#xff0c;Oracle等数据库&#xff0c;这些数据库都是由不同的团队开发的&#xff0c;所以相同的功能的api的名字不同&#xff0c;当一个后端工程需要切换一个数据库软件…

Java 实现敏感词检测

敏感词检测 敏感词的检测&#xff0c;一般是建立一个敏感词库&#xff0c;然后判断字符串中是否存在敏感词库中的某些词汇&#xff0c;然后将其过滤或者替换显示为其他文本&#xff0c;这对于一个和谐的网络环境是及其必要的&#xff0c;接下来就我们看看敏感词检测的实现方式…