找回密码
 立即注册
首页 业界区 安全 cpu 和垃圾回收的关系

cpu 和垃圾回收的关系

祝安芙 5 小时前
前言

前文中介绍了,cpu 使用率和三个方面有关,一个是中断、一个是用户时间、一个是系统时间。
那么当我们排查问题时候,查到是用户时间占用比较多的话,那么该如何排查呢?
正文

首先我们想到的是,cpu高嘛,查看火焰图对吧。 火焰图可以看到哪个函数消耗cpu比较多?
似乎这个问题好像马上能查看到,如果火焰图调入gc函数消耗的cpu比较多的话,那么就直接定位了。
然而事情好像没有这么简单。
这个我们来看一下火焰图原理:
火焰图是一种性能分析工具,它通过采样CPU调用栈来生成可视化的性能分析图表。火焰图能够显示:
1.CPU时间消耗最多的函数调用路径
2. 调用栈的深度和宽度
3. 热点代码的位置
GC在火焰图中的表现

  • GC线程活动:如果GC线程占用大量CPU时间,会在火焰图中显示为GC相关的调用栈
  • GC暂停时间:虽然火焰图主要显示CPU时间,但GC暂停期间的CPU使用也会被记录
  • 内存分配热点:频繁的内存分配会导致GC压力,这些分配点会在火焰图中显示
可能观察不到的情况:

  • 后台GC:.NET的Server GC模式使用后台线程进行垃圾回收,这些线程的活动可能不会在火焰图中明显显示
  • GC暂停:GC暂停期间应用程序线程被挂起,火焰图可能显示为"空闲"时间
也就是说火焰图可能能看出一些gc问题,但是有些可能不明显。
那么从简单的说,内存分配热点,也就是说内存频繁的分配,那么可以查看出。
这个其实占用cpu的话,经过测试,这个其实一般来说占用cpu不会太多,很难看出来。
比如说:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using System.Threading.Tasks;
  5. using System.Text;
  6. namespace RealAllocationHotspot
  7. {
  8.     class Program
  9.     {
  10.         static void Main(string[] args)
  11.         {
  12.             Console.WriteLine("Real Memory Allocation Hotspot Demo");
  13.             Console.WriteLine("This will show actual allocation hotspots in flame graphs");
  14.             
  15.             var cts = new CancellationTokenSource();
  16.             var tasks = new List<Task>();
  17.             // 任务1:CPU密集型的分配热点
  18.             tasks.Add(Task.Run(() => CpuIntensiveAllocation(cts.Token)));
  19.             // 任务2:字符串操作热点
  20.             tasks.Add(Task.Run(() => StringAllocationHotspot(cts.Token)));
  21.             // 任务3:集合操作热点
  22.             tasks.Add(Task.Run(() => CollectionAllocationHotspot(cts.Token)));
  23.             // 任务4:复杂对象分配热点
  24.             tasks.Add(Task.Run(() => ComplexObjectAllocation(cts.Token)));
  25.             Console.WriteLine("Press any key to stop...");
  26.             Console.ReadKey();
  27.             cts.Cancel();
  28.             Task.WaitAll(tasks.ToArray());
  29.         }
  30.         // CPU密集型的分配热点 - 会在火焰图中显示
  31.         static void CpuIntensiveAllocation(CancellationToken token)
  32.         {
  33.             while (!token.IsCancellationRequested)
  34.             {
  35.                 // 这些操作会消耗大量CPU时间
  36.                 for (int i = 0; i < 10000; i++)
  37.                 {
  38.                     // 热点1:大量字符串操作
  39.                     var result = "";
  40.                     for (int j = 0; j < 100; j++)
  41.                     {
  42.                         result += $"String_{i}_{j}_";  // 字符串连接
  43.                         result += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
  44.                     }
  45.                     // 热点2:字符串格式化
  46.                     var formatted = string.Format("Complex string: {0}, {1}, {2}, {3}, {4}",
  47.                         Guid.NewGuid(), DateTime.Now, Environment.TickCount,
  48.                         i);
  49.                     // 热点3:数组操作
  50.                     var array = new byte[1000];
  51.                     for (int k = 0; k < array.Length; k++)
  52.                     {
  53.                         array[k] = (byte)(i + k);
  54.                     }
  55.                 }
  56.             }
  57.         }
  58.         // 字符串分配热点 - 会显示为CPU热点
  59.         static void StringAllocationHotspot(CancellationToken token)
  60.         {
  61.             while (!token.IsCancellationRequested)
  62.             {
  63.                 // 这些操作会消耗CPU时间进行字符串处理
  64.                 for (int i = 0; i < 5000; i++)
  65.                 {
  66.                     // 热点:字符串连接操作
  67.                     var sb = new StringBuilder();
  68.                     for (int j = 0; j < 50; j++)
  69.                     {
  70.                         sb.Append($"String_{i}_{j}_");
  71.                         sb.Append(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
  72.                         sb.Append("_");
  73.                     }
  74.                     var result = sb.ToString();
  75.                     // 热点:字符串分割和重组
  76.                     var parts = result.Split('_');
  77.                     var reconstructed = string.Join("|", parts);
  78.                 }
  79.             }
  80.         }
  81.         // 集合分配热点 - 会显示为CPU热点
  82.         static void CollectionAllocationHotspot(CancellationToken token)
  83.         {
  84.             while (!token.IsCancellationRequested)
  85.             {
  86.                 // 这些操作会消耗CPU时间进行集合操作
  87.                 for (int i = 0; i < 1000; i++)
  88.                 {
  89.                     // 热点:列表操作
  90.                     var list = new List<string>();
  91.                     for (int j = 0; j < 100; j++)
  92.                     {
  93.                         list.Add($"Item_{i}_{j}");
  94.                     }
  95.                     // 热点:字典操作
  96.                     var dict = new Dictionary<string, object>();
  97.                     for (int j = 0; j < 50; j++)
  98.                     {
  99.                         dict[$"Key_{i}_{j}"] = new byte[100];
  100.                     }
  101.                     // 热点:集合操作
  102.                     var set = new HashSet<string>();
  103.                     for (int j = 0; j < 100; j++)
  104.                     {
  105.                         set.Add($"SetItem_{i}_{j}");
  106.                     }
  107.                 }
  108.             }
  109.         }
  110.         // 复杂对象分配热点 - 会显示为CPU热点
  111.         static void ComplexObjectAllocation(CancellationToken token)
  112.         {
  113.             while (!token.IsCancellationRequested)
  114.             {
  115.                 // 这些操作会消耗CPU时间进行对象创建和初始化
  116.                 for (int i = 0; i < 1000; i++)
  117.                 {
  118.                     // 热点:复杂对象创建
  119.                     var complexObject = new ComplexObject
  120.                     {
  121.                         Id = i,
  122.                         Name = $"ComplexObject_{i}",
  123.                         Data = new byte[500],
  124.                         Tags = new List<string> { "tag1", "tag2", "tag3" },
  125.                         Properties = new Dictionary<string, object>
  126.                         {
  127.                             ["prop1"] = Guid.NewGuid(),
  128.                             ["prop2"] = DateTime.Now,
  129.                             ["prop3"] = Environment.TickCount
  130.                         }
  131.                     };
  132.                     // 热点:对象序列化/反序列化模拟
  133.                     var serialized = complexObject.ToString();
  134.                     var deserialized = ComplexObject.FromString(serialized);
  135.                 }
  136.             }
  137.         }
  138.     }
  139.     // 复杂对象 - 用于演示分配热点
  140.     class ComplexObject
  141.     {
  142.         public int Id { get; set; }
  143.         public string Name { get; set; }
  144.         public byte[] Data { get; set; }
  145.         public List<string> Tags { get; set; }
  146.         public Dictionary<string, object> Properties { get; set; }
  147.         public override string ToString()
  148.         {
  149.             return $"ComplexObject{{Id={Id}, Name={Name}, DataLength={Data?.Length ?? 0}}}";
  150.         }
  151.         public static ComplexObject FromString(string str)
  152.         {
  153.             // 模拟反序列化
  154.             return new ComplexObject { Id = 0, Name = str };
  155.         }
  156.     }
  157. }
复制代码
那么来看一下cpu火焰图:
1.png

可以看到一个datetime的now,后面都是大量的扩容创建对象啥的。
GC暂停时间:虽然火焰图主要显示CPU时间,但GC暂停期间的CPU使用也会被记录。
这个怎么说呢?
// .NET中有两种GC模式:
// 1. 工作站GC (Workstation GC) - 前台GC,会暂停所有线程
// 2. 服务器GC (Server GC) - 后台GC,使用专用线程
然后这两种工作状态呢?
  1. // GC暂停期间,应用程序线程的状态:
  2. // 1. 前台GC:所有线程被挂起
  3. // 2. 后台GC:应用程序线程继续运行,GC线程在后台工作
复制代码
这里说的是gc暂停时间呢?说的是前台gc,所有的线程被挂起的情况。
前台gc暂停:
明显可见:前台GC会暂停所有用户线程,在火焰图中表现为明显的"热点"
具体表现:
GC.Collect() 调用会显示为CPU热点
GC.WaitForPendingFinalizers() 会显示为等待时间
用户线程在GC期间会显示为 WaitHandle.WaitOne 或 Thread.Sleep
GC.Collect() 和 GC.WaitForPendingFinalizers() 是gc回收的函数。
[code]using System;using System.Collections.Generic;using System.Diagnostics;using System.Threading;using System.Threading.Tasks;namespace GCPauseFlameDemo{    ///     /// 演示GC暂停在火焰图中的不同表现    ///     public class GCFlameGraphDemo    {        public static void RunDemo()        {            Console.WriteLine("=== GC暂停在火焰图中的表现演示 ===");            Console.WriteLine();            Console.WriteLine("请选择测试模式:");            Console.WriteLine("1. 前台GC演示 - 会明显显示在火焰图中");            Console.WriteLine("2. 后台GC演示 - 可能不明显");            Console.WriteLine("3. 业务逻辑演示 - 在GC暂停期间会受到影响");            Console.WriteLine("4. 密集GC演示 - 专门设计用于在火焰图中产生明显热点");            Console.WriteLine("5. 超激进GC演示 - 尝试在用户线程上产生明显的GC相关CPU使用");            Console.WriteLine("6. GC活动诊断 - 监控GC相关指标并解释火焰图表现");            Console.WriteLine("7. 混合模式 - 所有任务同时运行(用于对比)");            Console.WriteLine("8. 分阶段测试 - 依次测试每种模式");            Console.WriteLine();            Console.Write("请输入选择 (1-8): ");                        var choice = Console.ReadLine();                        switch (choice)            {                case "1":                    RunForegroundGCDemo();                    break;                case "2":                    RunBackgroundGCDemo();                    break;                case "3":                    RunBusinessLogicDemo();                    break;                case "4":                    RunIntensiveGCDemo();                    break;                case "5":                    RunUltraIntensiveGCDemo();                    break;                case "6":                    RunDiagnoseGCActivity();                    break;                case "7":                    RunMixedDemo();                    break;                case "8":                    RunStagedDemo();                    break;                default:                    Console.WriteLine("无效选择,运行混合模式演示...");                    RunMixedDemo();                    break;            }        }        ///         /// 演示前台GC - 在火焰图中会显示为明显的热点        ///         static void DemonstrateForegroundGC(CancellationToken token)        {            Console.WriteLine("前台GC演示开始 - 这些操作会在火焰图中显示为热点");                        while (!token.IsCancellationRequested)            {                // 大量分配内存,确保触发GC                var objects = new List();                for (int i = 0; i < 200000; i++)  // 增加分配量                {                    objects.Add(new byte[2000]); // 分配400MB                }                // 强制触发前台GC - 这会在火焰图中显示为CPU热点                var sw = Stopwatch.StartNew();                                // 多次调用GC,确保在火焰图中可见                for (int i = 0; i < 5; i++)  // 连续调用5次                {                    GC.Collect(0, GCCollectionMode.Forced);                    GC.WaitForPendingFinalizers();                                        // 短暂休眠,让采样器有机会捕获                    Thread.Sleep(10);                }                                sw.Stop();                                Console.WriteLine($"[前台GC] 强制GC耗时: {sw.ElapsedMilliseconds}ms");                                // 减少休眠时间,增加GC调用频率                Thread.Sleep(200);            }        }        ///         /// 演示后台GC - 在火焰图中可能不明显        ///         static void DemonstrateBackgroundGC(CancellationToken token)        {            Console.WriteLine("后台GC演示开始 - 这些操作在火焰图中可能不明显");                        while (!token.IsCancellationRequested)            {                // 大量分配,让后台GC自然触发                var objects = new List();                for (int i = 0; i < 50000; i++)                {                    objects.Add(new byte[2000]); // 分配100MB                }                // 不强制GC,让后台GC线程自然工作                // 后台GC线程的工作可能不会在火焰图中明显显示                                Thread.Sleep(300);            }        }        ///         /// 演示业务逻辑在GC暂停期间的表现        ///         static void DemonstrateBusinessLogic(CancellationToken token)        {            Console.WriteLine("业务逻辑演示开始 - 在GC暂停期间会受到影响");                        while (!token.IsCancellationRequested)            {                // 模拟正常的业务逻辑                var sw = Stopwatch.StartNew();                                // 密集计算                for (int i = 0; i < 10000; i++)                {                    var result = Math.Sqrt(i) * Math.PI;                }                                sw.Stop();                                // 如果业务逻辑在GC暂停期间执行,可能会显示为:                // - Thread.Sleep (等待GC完成)                // - WaitHandle.WaitOne (等待GC完成)                // - 执行时间异常长                                Console.WriteLine($"[业务逻辑] 计算耗时: {sw.ElapsedMilliseconds}ms");                                Thread.Sleep(100);            }        }        ///         /// 专门用于在火焰图中产生明显GC热点的方法        ///         static void DemonstrateIntensiveGC(CancellationToken token)        {            Console.WriteLine("密集GC演示开始 - 专门设计用于在火焰图中产生明显热点");            Console.WriteLine("注意:GC工作主要在专用线程上,可能不会在用户线程的火焰图中显示");                        while (!token.IsCancellationRequested)            {                // 创建大量对象,确保触发GC                var objects = new List();                for (int i = 0; i < 500000; i++)  // 大量分配                {                    objects.Add(new byte[1000]); // 分配500MB                }                // 密集调用GC,确保在火焰图中可见                var sw = Stopwatch.StartNew();                                // 连续多次GC,产生明显的CPU热点                for (int j = 0; j < 10; j++)  // 10次循环                {                    // 强制Gen0 GC                    GC.Collect(0, GCCollectionMode.Forced);                    GC.WaitForPendingFinalizers();                                        // 强制Gen1 GC                    GC.Collect(1, GCCollectionMode.Forced);                    GC.WaitForPendingFinalizers();                                        // 强制Gen2 GC(最耗时)                    GC.Collect(2, GCCollectionMode.Forced);                    GC.WaitForPendingFinalizers();                                        // 短暂休眠,让采样器捕获                    Thread.Sleep(5);                }                                sw.Stop();                                Console.WriteLine($"[密集GC] 强制GC耗时: {sw.ElapsedMilliseconds}ms");                                // 短暂休眠后继续                Thread.Sleep(100);            }        }        ///         /// 超激进GC测试 - 尝试在用户线程上产生明显的GC相关CPU使用        ///         static void DemonstrateUltraIntensiveGC(CancellationToken token)        {            Console.WriteLine("超激进GC演示开始 - 尝试在用户线程上产生明显的GC相关CPU使用");            Console.WriteLine("这个测试会尝试在用户线程上执行GC相关工作");                        while (!token.IsCancellationRequested)            {                // 创建大量对象                var objects = new List();                for (int i = 0; i < 1000000; i++)  // 1M个对象                {                    objects.Add(new byte[1000]); // 分配1GB                }                var sw = Stopwatch.StartNew();                                // 在用户线程上执行密集的GC相关操作                for (int j = 0; j < 20; j++)  // 20次循环                {                    // 连续调用GC,尝试在用户线程上产生CPU热点                    for (int k = 0; k < 5; k++)                    {                        GC.Collect(0, GCCollectionMode.Forced);                        GC.WaitForPendingFinalizers();                                                // 在用户线程上执行一些计算,确保CPU使用                        for (int l = 0; l < 10000; l++)                        {                            var result = Math.Sqrt(l) * Math.PI;                        }                    }                                        // 强制Gen1和Gen2 GC                    GC.Collect(1, GCCollectionMode.Forced);                    GC.WaitForPendingFinalizers();                                        GC.Collect(2, GCCollectionMode.Forced);                    GC.WaitForPendingFinalizers();                                        // 在用户线程上执行更多计算                    for (int l = 0; l < 5000; l++)                    {                        var result = Math.Sin(l) * Math.Cos(l);                    }                }                                sw.Stop();                                Console.WriteLine($"[超激进GC] 强制GC耗时: {sw.ElapsedMilliseconds}ms");                Console.WriteLine($"当前内存使用: {GC.GetTotalMemory(false) / 1024 / 1024} MB");                                Thread.Sleep(50);            }        }        ///         /// 诊断GC活动的方法        ///         static void DiagnoseGCActivity(CancellationToken token)        {            Console.WriteLine("GC活动诊断开始 - 监控GC相关指标");                        var lastGen0 = GC.CollectionCount(0);            var lastGen1 = GC.CollectionCount(1);            var lastGen2 = GC.CollectionCount(2);            var lastMemory = GC.GetTotalMemory(false);            while (!token.IsCancellationRequested)            {                var currentGen0 = GC.CollectionCount(0);                var currentGen1 = GC.CollectionCount(1);                var currentGen2 = GC.CollectionCount(2);                var currentMemory = GC.GetTotalMemory(false);                if (currentGen0 > lastGen0 || currentGen1 > lastGen1 || currentGen2 > lastGen2)                {                    Console.WriteLine($"\n=== GC事件检测到 [{DateTime.Now:HH:mm:ss.fff}] ===");                    Console.WriteLine($"Gen0: {currentGen0 - lastGen0} 次");                    Console.WriteLine($"Gen1: {currentGen1 - lastGen1} 次");                    Console.WriteLine($"Gen2: {currentGen2 - lastGen2} 次");                    Console.WriteLine($"内存变化: {(currentMemory - lastMemory) / 1024 / 1024} MB");                    Console.WriteLine($"总内存: {currentMemory / 1024 / 1024} MB");                                        // 解释为什么GC可能不在火焰图中显示                    Console.WriteLine("
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册