G1日志解读

本文深入解析G1垃圾回收器的工作原理,涵盖年轻代、并发及混合垃圾回收的流程与日志解读,助您理解G1如何高效管理内存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

启动 Java 服务时带上参数-Xloggc:/home/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps会打印 GC 的细节到文件 /home/gc.log。另外通过-XX:+UnlockDiagnosticVMOptions -XX:+G1PrintRegionLivenessInfo参数可以打印 G1 在清除阶段每个 Region 存活对象信息

1. G1 年轻代垃圾回收

1.1 Yong GC 日志样例

一次典型的G1 收集器 YGC如下。这是一个STW,所有的应用线程停止在安全点,日志通过缩进来区分子任务

2020-04-15T09:54:03.706+0800: 153105.710: [GC pause (G1 Evacuation Pause) (young), 0.0079319 secs]
   [Parallel Time: 4.8 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 153105710.1, Avg: 153105711.9, Max: 153105713.8, Diff: 3.7]
      [Ext Root Scanning (ms): Min: 0.3, Avg: 2.2, Max: 4.7, Diff: 4.4, Sum: 9.0]
      [Update RS (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 0.9]
         [Processed Buffers: Min: 0, Avg: 5.8, Max: 13, Diff: 13, Sum: 23]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.1, Avg: 0.4, Max: 0.7, Diff: 0.6, Sum: 1.5]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.2]
         [Termination Attempts: Min: 1, Avg: 1.5, Max: 2, Diff: 1, Sum: 6]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 1.1, Avg: 2.9, Max: 4.7, Diff: 3.7, Sum: 11.6]
      [GC Worker End (ms): Min: 153105714.8, Avg: 153105714.8, Max: 153105714.8, Diff: 0.0]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 1.8 ms]
   [Other: 1.3 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 1.2 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 24.0M(24.0M)->0.0B(24.0M) Survivors: 1024.0K->1024.0K Heap: 81.6M(128.0M)->57.6M(128.0M)]
 [Times: user=0.01 sys=0.00, real=0.01 secs]

1. 2 日志解读

  1. 首行蓝色字体为 GC 开始的时间,绿色字体为GC 开始时距离 JVM 启动的秒数,红色字体表明这是G1的 Evacuation Pause(转移暂停),在这个阶段年轻代分区存活的对象被从一个分区拷贝到另一个分区,最后是花费的时间

    2020-04-15T09:54:03.706+0800: 153105.710: [GC pause (G1 Evacuation Pause) (young), 0.0079319 secs]

  2. 这部分列出了新生代收集中并行收集的详细过程

    • Parallel Time
      并行收集任务在运行过程中引发的STW(Stop The World)时间,从新生代垃圾收集开始到最后一个任务结束,共花费4.8 ms
    • GC Workers
      4 个线程负责垃圾收集,线程数可通过参数-XX:ParallelGCThreads设置。这个参数的值的设置跟CPU有关,如果物理CPU支持的线程个数小于8,则最多设置为8;如果物理CPU支持的线程个数大于8,则默认值为支持的线程个数 * 5 / 8
    • GC Worker Start
      Min:垃圾收集线程开始工作时 JVM 启动后经过的最小时间
      Avg:垃圾收集线程开始工作时 JVM 启动后经过的平均时间
      Max:垃圾收集线程开始工作时 JVM 启动后经过的最大时间
      Diff:表示 Min 和 Max 之间的差值,理想情况下所有 GC 线程几乎是同时开始工作,即 Diff 趋近于0
    • Ext Root Scanning
      扫描 线程栈、JNI、全局变量、系统表等root根节点集合花费的时间,扫描 root 集合是垃圾收集的起点,该步骤扫描是否有root集合中的节点指向当前的收集集合(CSet),即对象是否可达
    • Update RS
      每个分区都有自己的RSet(Remembered Set),用来记录其他分区指向当前分区的指针。如果RSet有更新,G1中会有一个post-write barrier管理跨分区的引用:新的被引用的 card 会被标记为 dirty,并放入一个日志缓冲区,如果这个日志缓冲区满了会被加入到一个全局的缓冲区。在 JVM 运行的过程中还有线程在并发处理这个全局日志缓冲区的dirty cardUpdate RS表示允许垃圾收集线程处理本次垃圾收集开始前没有处理好的日志缓冲区,这可以确保当前分区的 RSet 是最新的

      Processed Buffers表明在Update RS这个过程中处理多少个日志缓冲区

    • Scan RS
      扫描每个新生代分区的 RSet,找出有多少指向当前分区的引用来自 CSet 花费的时间
    • Code Root Scanning
      扫描代码中的 局部变量root节点花费的时间
    • Object Copy
      在暂停期间,所有在 CSet 中的分区的存活对象必须被转移疏散,该步骤就负责将当前分区中存活的对象拷贝到新的分区
    • Termination
      当一个垃圾收集线程完成任务时,它就会进入一个临界区,并尝试帮助其他垃圾线程完成任务(steal outstanding tasks),Min表示该垃圾收集线程什么时候尝试结束工作,Max表示该垃圾收集回收线程什么时候真正结束工作

      Termination Attempts
      如果一个垃圾收集线程成功帮助其他线程完成任务,那么它会再次盗取更多的任务或尝试结束工作,每次重新尝试结束工作的时候,这个数值就会增加

    • GC Worker Other
      垃圾收集线程在完成其他任务的时间
    • GC Worker Total
      每个垃圾收集线程的最小、最大、平均、差值和总共时间
    • GC Worker End
      Min表示最早结束的垃圾收集线程结束时该JVM启动后的时间;Max表示最晚结束的垃圾收集线程结束时该JVM启动后的时间。理想情况下各个线程最好快速结束,并且最好是同一时间结束
    [Parallel Time: 4.8 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 153105710.1, Avg: 153105711.9, Max: 153105713.8, Diff: 3.7]
      [Ext Root Scanning (ms): Min: 0.3, Avg: 2.2, Max: 4.7, Diff: 4.4, Sum: 9.0]
      [Update RS (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 0.9]
         [Processed Buffers: Min: 0, Avg: 5.8, Max: 13, Diff: 13, Sum: 23]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.1, Avg: 0.4, Max: 0.7, Diff: 0.6, Sum: 1.5]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.2]
         [Termination Attempts: Min: 1, Avg: 1.5, Max: 2, Diff: 1, Sum: 6]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 1.1, Avg: 2.9, Max: 4.7, Diff: 3.7, Sum: 11.6]
      [GC Worker End (ms): Min: 153105714.8, Avg: 153105714.8, Max: 153105714.8, Diff: 0.0] 
    
  3. 列出新生代 GC 中的一些任务

    • Code Root Fixup
      释放用于管理并行垃圾收集活动的数据结构,应该接近于0,该步骤是线性执行
    • Code Root Purge
      清理更多的数据结构,应该很快,耗时接近于0,也是线性执行
    • Clear CT
      清理card table
    • Other

      Choose CSet:选择要进行回收的分区放入CSet(G1选择的标准是垃圾最多的分区优先,也就是存活对象率最低的分区优先)
      Ref Proc:处理Java中的各种引用——soft、weak、final、phantom、JNI等等
      Ref Enq:遍历所有的引用,将不能回收的放入pending列表
      Redirty Card:在回收过程中被修改的card将会被重置为dirty
      Humongous Register:巨型对象在新生代收集的时候被回收花费的时间,通过参数XX:+G1EagerReclaimHumongousObjects可设置是否在YGC时回收巨型对象,默认为 true
      Humongous Reclaim:完成这些任务的时间:确保巨型对象可以被回收、释放该巨型对象所占的分区,重置分区类型,并将分区还到 free 列表,并且更新空闲空间大小
      Free CSet:将要释放的分区还回到 free 列表

    [Code Root Fixup: 0.0 ms]
    [Code Root Purge: 0.0 ms]
    [Clear CT: 1.8 ms]
    [Other: 1.3 ms]
       [Choose CSet: 0.0 ms]
       [Ref Proc: 1.2 ms]
       [Ref Enq: 0.0 ms]
       [Redirty Cards: 0.0 ms]
       [Humongous Register: 0.0 ms]
       [Humongous Reclaim: 0.0 ms]
       [Free CSet: 0.0 ms]
    
  4. 不同代的内存空间大小变化,以及堆大小的自适应调整

      [Eden: 24.0M(24.0M)->0.0B(24.0M) Survivors: 1024.0K->1024.0K Heap: 81.6M(128.0M)->57.6M(128.0M)]
    [Times: user=0.01 sys=0.00, real=0.01 secs]
    

2. G1 并发垃圾收集

2.1 日志样例

2020-04-16T09:20:50.450+0800: 62599.287: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0051579 secs]
   ......
   [Eden: 53.0M(53.0M)->0.0B(55.0M) Survivors: 2048.0K->1024.0K Heap: 111.2M(128.0M)->57.9M(128.0M)]
 [Times: user=0.01 sys=0.00, real=0.01 secs]
2020-04-16T09:20:50.456+0800: 62599.292: [GC concurrent-root-region-scan-start]
2020-04-16T09:20:50.456+0800: 62599.292: Total time for which application threads were stopped: 0.0061480 seconds, Stopping threads took: 0.0001037 seconds
2020-04-16T09:20:50.458+0800: 62599.294: [GC concurrent-root-region-scan-end, 0.0025203 secs]
2020-04-16T09:20:50.458+0800: 62599.294: [GC concurrent-mark-start]
2020-04-16T09:20:50.572+0800: 62599.408: [GC concurrent-mark-end, 0.1137222 secs]
2020-04-16T09:20:50.573+0800: 62599.409: [GC remark 2020-04-16T09:20:50.573+0800: 62599.409: [Finalize Marking, 0.0002817 secs] 2020-04-16T09:20:50.573+0800: 62599.409: [GC ref-proc, 0.0019343 secs] 2020-04-16T09:20:50.575+0800: 62599.411
: [Unloading, 0.0315269 secs], 0.0340811 secs]
 [Times: user=0.09 sys=0.00, real=0.03 secs]
2020-04-16T09:20:50.607+0800: 62599.443: Total time for which application threads were stopped: 0.0349111 seconds, Stopping threads took: 0.0000578 seconds
2020-04-16T09:20:50.608+0800: 62599.444: [GC cleanup 60M->60M(128M), 0.0009348 secs]
 [Times: user=0.01 sys=0.00, real=0.00 secs]

2.2 日志解读

  1. 并发垃圾收集阶段的开始
    GC pause(G1 Evacuation Pause)(young)(initial-mark):为了充分利用 STW 的机会来扫描所有可达对象,initial-mark 阶段是作为新生代垃圾收集中的一部分存在的。initial-mark设置了两个 TAMS(top-at-mark-start)变量用来区分存活的对象和在并发标记阶段新分配的对象,在TAMS之前的所有对象在当前周期内都会被视作存活的

  2. 并发标记阶段做的第一件事情:根分区扫描
    GC concurrent-root-region-scan-start:根分区扫描开始,根分区扫描主要扫描的是新的 survivor 分区,找到这些分区内的对象指向当前分区的引用,如果发现有引用,则做个记录
    GC concurrent-root-region-scan-end:根分区扫描结束

  3. 并发标记阶段
    GC Concurrent-mark-start:并发标记阶段开始

    (1)并发标记阶段的线程是跟应用线程一起运行的,不会STW,所以称为并发;并发标记阶段的垃圾收集线程,默认值是Parallel Thread个数的25%,这个值也可以用参数-XX:ConcGCThreads设置
    (2)trace整个堆,并使用位图标记所有存活的对象,因为在top TAMS之前的对象是隐式存活的,所以这里只需要标记出那些在top TAMS之后、阈值之前的
    (3)记录在并发标记阶段的变更,G1这里使用了SATB算法,该算法要求在垃圾收集开始的时候给堆做一个快照,在垃圾收集过程中这个快照是不变的,但实际上肯定有些对象的引用会发生变化,这时候G1使用了pre-write barrier记录这种变更,并将这个记录存放在一个SATB缓冲区中,如果该缓冲区满了就会将它加入到一个全局的缓冲区,同时G1有一个线程在并行得处理这个全局缓冲区
    (4)在并发标记过程中,会记录每个分区的存活对象占整个分区的大小的比率

    GC Concurrent-mark-end:并发标记阶段结束

  4. 重新标记阶段,会 Stop the World
    Finalize Marking:Finalizer列表里的Finalizer对象处理
    GC ref-proc:引用(soft、weak、final、phantom、JNI等等)处理
    Unloading:类卸载
    这个阶段最关键的作用扫描出当前并发周期中整个堆的最后状态,剩余的SATB缓冲区会在这里被处理,所有存活的对象都会被标记

  5. 清理阶段,会Stop the World

    • 计算出最后存活的对象
      标记出initial-mark阶段后分配的对象;标记出至少有一个存活对象的分区
    • 没有存活对象的老年代分区和巨型对象分区会被释放和清理
    • 处理没有任何存活对象的分区的RSet
    • 所有的老年代分区会按照自己的存活率(存活对象占整个分区大小的比例)进行排序,为后面的CSet选择过程做准备
  6. 并发清理阶段

    • GC concurrent-cleanup-start
      完成第5步剩余的清理工作;将完全清理好的分区加入到二级free列表,等待最终还到总体的free列表
    • GC concurrent-cleanup-end:并发清理阶段结束

3. G1 混合垃圾回收

混合收集阶段的日志如下所示,这份日志的大部分与新生代收集相同,只是首行不同:GC pause(G1 Evacuation Pause)(mixed)。这些表示这是一个混合垃圾收集周期,在这个阶段处理的CSet不仅包括新生代的分区,还包括老年代分区也就是并发标记阶段标记出来的那些老年代分区

2020-04-16T09:48:03.283+0800: 64232.120: [GC pause (G1 Evacuation Pause) (mixed), 0.0063376 secs]
   [Parallel Time: 5.4 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 64232119.9, Avg: 64232119.9, Max: 64232119.9, Diff: 0.0]
      [Ext Root Scanning (ms): Min: 1.5, Avg: 1.9, Max: 2.5, Diff: 1.0, Sum: 7.4]
      [Update RS (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 0.6]
         [Processed Buffers: Min: 0, Avg: 2.5, Max: 8, Diff: 8, Sum: 10]
      [Scan RS (ms): Min: 0.5, Avg: 0.8, Max: 1.0, Diff: 0.5, Sum: 3.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.6, Max: 2.5, Diff: 2.5, Sum: 2.5]
      [Object Copy (ms): Min: 0.3, Avg: 1.9, Max: 2.7, Diff: 2.4, Sum: 7.8]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Termination Attempts: Min: 1, Avg: 2.2, Max: 4, Diff: 3, Sum: 9]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Total (ms): Min: 5.4, Avg: 5.4, Max: 5.4, Diff: 0.0, Sum: 21.5]
      [GC Worker End (ms): Min: 64232125.3, Avg: 64232125.3, Max: 64232125.3, Diff: 0.0]
   [Code Root Fixup: 0.1 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.7 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.3 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.1 ms]
   [Eden: 5120.0K(5120.0K)->0.0B(63.0M) Survivors: 1024.0K->1024.0K Heap: 62.7M(128.0M)->50.3M(128.0M)]
 [Times: user=0.02 sys=0.00, real=0.00 secs]
<think>我们正在处理关于spdlog日志库源码解读的问题。用户希望了解spdlog的源码结构、关键机制和实现细节。根据提供的引用片段,我们可以总结出以下几个关键点:1.**日志消息处理流程**:引用[1]展示了`async_logger`如何将日志消息投递到线程池的消息队列中(`sink_it_`方法)。2.**日志格式化**:引用[2]详细说明了模式字符串的解析和占位符替换机制。3.**logger创建**:引用[3]展示了如何通过`basic_logger_mt`模板函数创建logger。4.**线程池任务分配**:引用[4]给出了一个简化的线程池任务分配示例(注意:此示例并非spdlog源码,但概念类似)。5.**日志消息结构**:引用[5]介绍了`log_msg`类的成员。基于这些信息,我们将系统性地解读spdlog的核心设计。###spdlog源码核心设计解读####1.日志消息(log_msg)spdlog中每条日志消息封装为`log_msg`对象,包含以下关键字段:-`logger_name`:日志记录器名称-`level`:日志级别(如debug,info,error等)-`time`:时间戳(默认为当前时间)-`thread_id`:线程ID(可通过宏定义禁用)-`source`:源代码位置(文件名、行号、函数名)-`payload`:日志正文内容该结构体定义类似于:```cppstructlog_msg{std::stringlogger_name;level::level_enumlevel;log_clock::time_pointtime;size_tthread_id;source_locsource;fmt::string_viewpayload;};```####2.日志记录器(logger)日志记录器是用户直接交互的接口。spdlog提供两种主要类型的logger:-**同步logger**:直接在当前线程执行日志写入操作-**异步logger**:通过线程池异步写入日志创建logger的典型方式(引用[3]):```cpp//创建多线程安全的文件loggerautofile_logger=spdlog::basic_logger_mt("file_logger","logs/basic.txt");```####3.异步日志处理机制异步logger的核心在于将日志消息投递到线程池(引用[1]):```cppvoidasync_logger::sink_it_(constdetails::log_msg&msg){if(autopool_ptr=thread_pool_.lock()){//将日志消息提交到线程池队列pool_ptr->post_log(shared_from_this(),msg,overflow_policy_);}else{throw_spdlog_ex("asynclog:threadpooldoesn'texistanymore");}}```此处`post_log`方法将日志任务加入线程池队列,由工作线程异步执行实际的sink操作。####4.日志格式化(pattern_formatter)spdlog通过`pattern_formatter`解析用户定义的模式字符串(如`"[%Y-%m-%d%H:%M:%S][%l]%v"`),其工作流程如下:1.**解析模式字符串**:识别占位符(如`%Y`、`%l`)和普通文本2.**生成格式化项**:每个占位符对应一个格式化项(formatter)3.**格式化消息**:遍历格式化项,将`log_msg`中的数据转换为字符串关键占位符类型(引用[2]):-时间相关:`%Y`(年)、`%m`(月)、`%d`(日)、`%H`(时)、`%M`(分)、`%S`(秒)-日志级别:`%l`(完整级别名)、`%L`(简短级别名)-消息正文:`%v`-线程ID:`%t`-源码位置:`%s`(短文件名)、`%g`(全文件名)、`%#`(行号)、`%!`(函数名)####5.线程池设计spdlog的异步日志依赖于线程池(`thread_pool`),其核心结构包括:-**任务队列**:存储待处理的日志消息-**工作线程组**:多个线程从队列中取出任务并执行-**溢出策略**:当队列满时的处理方式(如阻塞或丢弃日志)线程池提交任务的简化逻辑(引用[4]概念):```cppvoidthread_pool::post_log(/*...*/){//将日志任务封装为可调用对象autotask=[logger,msg](){logger->sink_it_(msg);//实际执行日志写入};queue_.enqueue(std::move(task));//加入任务队列}```####6.sink机制sink负责最终输出日志,常见类型包括:-控制台sink(`stdout_color_sink_mt`)-文件sink(`basic_file_sink`)-旋转文件sink(`rotating_file_sink`)每个sink需要实现`sink_it_(constlog_msg&)`方法,例如文件sink会打开文件并写入字符串。###总结spdlog的设计围绕以下核心展开:1.**高效异步**:通过线程池实现非阻塞日志写入2.**灵活格式化**:支持自定义模式字符串3.**模块化sink**:可扩展的输出目标支持4.**类型安全**:基于fmt库的格式化机制§§相关问题§§1.spdlog如何处理日志格式化的性能优化?2.异步日志模式下如何避免消息丢失?3.spdlog如何实现日志文件的滚动(rotation)?4.在多进程写入同一日志文件时spdlog如何保证安全性?</think>### spdlog源码核心设计解读 spdlog是一个高性能C++日志库,其源码设计围绕**线程安全**、**异步处理**和**模块化扩展**三大核心展开。以下是关键机制解析: #### 1. 异步日志处理机制 核心类`async_logger`通过线程池实现非阻塞日志写入: ```cpp void async_logger::sink_it_(const details::log_msg &msg) { if (auto pool_ptr = thread_pool_.lock()) { pool_ptr->post_log(shared_from_this(), msg, overflow_policy_); } else { throw_spdlog_ex("async log: thread pool doesn't exist anymore"); } } ``` - **线程池调度**:`post_log`将日志任务投递到线程池队列(引用[1]) - **消息封装**:`log_msg`包含时间戳、线程ID、日志级别等元数据(引用[5]) - **溢出策略**:`overflow_policy_`控制队列满时的行为(阻塞/丢弃) #### 2. 格式化引擎设计 `pattern_formatter`实现高效的占位符解析: ```cpp // 模式字符串示例: "[%Y-%m-%d %H:%M:%S] [%l] %v" void pattern_formatter::format(...) { // 解析占位符并替换实际值 switch (flag) { case 'Y': // 年份 case 't': // 线程ID case 'v': // 消息正文 // ...其他占位符处理 } } ``` 支持40+种占位符类型(引用[2]): - 时间类:`%Y`(年), `%S`(秒), `%f`(毫秒) - 上下文类:`%t`(线程ID), `%s`(源文件名) - 日志类:`%l`(完整级别), `%L`(简短级别) #### 3. 模块化sink架构 sink是实际输出设备(文件/控制台等)的抽象基类: ```cpp class sink { public: virtual void log(const details::log_msg& msg) = 0; virtual ~sink() = default; }; ``` - **多sink支持**:一个logger可绑定多个sink - **线程安全**:`sink_mt`提供互斥锁保护的线程安全版本 - **自定义扩展**:通过继承实现网络sink等自定义输出 #### 4. 核心对象关系 $$ \text{logger} \xrightarrow{\text{持有}} \text{sinks} \xrightarrow{\text{输出}} \text{目标设备} $$ $$ \text{async\_logger} \xrightarrow{\text{委托}} \text{thread\_pool} \xrightarrow{\text{异步执行}} \text{sink\_操作} $$ #### 5. 性能优化点 1. **内存池**:预分配`log_msg`对象减少动态内存分配 2. **无锁队列**:异步模式使用无锁队列(如moodycamel::ConcurrentQueue) 3. **格式化延迟**:仅在需要时执行字符串格式化 4. **批量提交**:线程池支持批量任务处理 ### 典型工作流程 1. 用户调用`spdlog::info("message")` 2. 构造`log_msg`对象(包含时间戳、线程ID等) 3. 同步模式:直接调用sink输出 4. 异步模式:提交任务到线程池队列 5. 工作线程从队列取出任务 6. 执行`pattern_formatter`格式化消息 7. sink将格式化结果写入目标设备 > 提示:通过`SPDLOG_LEVEL_TRACE`编译选项可启用内部调试日志(引用[3])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值