前言

在现代的容器化和微服务应用中,因为分布式的环境和错综复杂的调用关系,APM(Application Performance Monitoring 应用性能监控)显得尤为重要,它通过采集应用程序各种指标和请求链路,让你知道系统当前的状态和值得优化的点,另外能帮助你发现应用程序的异常,帮助你更方便的定位问题。

对于.NET这样带GC(Garbage Collector 垃圾回收器)的平台来说,GC的指标也尤为重要,采集可以帮助我们分析内存泄漏、优化系统性能等等。在公司内部已经可以采集比较全面的.NET GC指标,如下图所示。



在绝大多数场景它能满足要求,但是如果遇到某时某刻P95延时突然增大,异步任务突然超时,我们想排查这些异常是否因为GC的STW Time(Stop The World Time 指GC运行过程中所有线程被挂起的时间)过长导致的,就没有办法了,因为目前没有采集这些指标。

所以本文就带大家了解一下,如何采集.NET GC STW Time。

方法

.NET内存性能分析指南中提到的一样,.NET Runtime在运行过程中会发布很多事件,这些事件代表了当前Runtime的运行状态,同样GC在运行过程中也会发布很多事件,我们可以使用PerfView工具来收集这样的一些事件。下面是WorkStationGC发生GC时的一个事件序列。

Microsoft-Windows-DotNETRuntime/GC/SuspendEEStart	//开始暂停托管线程运行
Microsoft-Windows-DotNETRuntime/GC/SuspendEEStop //暂停托管线程完成
Microsoft-Windows-DotNETRuntime/GC/Start // GC开始回收
Microsoft-Windows-DotNETRuntime/GC/Stop // GC回收结束
Microsoft-Windows-DotNETRuntime/GC/RestartEEStart //恢复之前暂停的托管线程
Microsoft-Windows-DotNETRuntime/GC/RestartEEStop //恢复托管线程运行完成

PS: 所有的事件都可以在.NET文档官方中找到,非常的全面。

SuspendEEStart(暂停托管线程运行)RestartEEStop(恢复托管线程运行完成)中经过的时间就是STW Time,我们只需要记录这两个事件的差值,就可以知道本次GC STW的时间有多长。

BGC的过程比WorkStationGC复杂的很多,但是一样是测量这两个事件花费的时间来采集STW Time,本文不做过多介绍。

使用EventSource采集

那么我们知道通过计算哪两个指标的差值来获得STW时间,那么应该如何通过代码来采集呢?

这里就需要知道EventSourceEventListener两个类,顾名思义我们可以通过EventSource来发布事件,使用EventListener来监听事件,在本文中我们也主要使用EventListener来收集GC事件,对于这EventSource类的使用大家可以看下面给出的微软文档链接,这里不做过多介绍。

using System.Diagnostics.Tracing;  

// 开启GC事件监听
var gc = new GcStwMetricsCollector();
// 创建一些对象
var array = Enumerable.Range(0, 1000).Select(s => (decimal)s).ToArray();
// 手动执行GC
GC.Collect();
Console.ReadLine(); public class GcStwMetricsCollector : EventListener
{
// GC关键字
private const int GC_KEYWORD = 0x0000001;
// 我们要关注的GC事件
private const int GCSuspendEEBegin = 9;
private const int GCRestartEEEnd = 3; private EventSource? _eventSource;
public void Stop()
{
if (_eventSource == null)
return; DisableEvents(_eventSource);
} protected override void OnEventSourceCreated(EventSource eventSource)
{
_eventSource = eventSource;
// GC 事件在 Microsoft-Windows-DotNETRuntime 名称空间下
if (eventSource.Name.Equals("Microsoft-Windows-DotNETRuntime"))
{
// 启用事件,事件级别为Informational, 只监听GC事件
EnableEvents(eventSource, EventLevel.Informational, (EventKeywords) (GC_KEYWORD));
}
} private long _currentStwStartTime = 0;
protected override void OnEventWritten(EventWrittenEventArgs e)
{
switch (e.EventId)
{
// 冻结托管线程开始,记录当前时间
case GCSuspendEEBegin:
_currentStwStartTime = e.TimeStamp.Ticks;
break;
// 恢复托管线程结束,计算当前时间与冻结托管线程开始时间的差值
case GCRestartEEEnd:
if (_currentStwStartTime > 0)
{
var ms = TimeSpan.FromTicks(e.TimeStamp.Ticks - _currentStwStartTime).TotalMilliseconds;
_currentStwStartTime = 0;
// 输出结果
Console.WriteLine($"STW: {ms}ms");
}
break;
}
}
}

运行结果:

STW: 0.2568ms

至于GC事件对应的枚举值,大家可以在我上文中给出的文档中找到。

.NET7新API

在实现这个需求时,我注意到.NET7有一个新的issue,直接提供了一个API,让我们可以获取到总的GC STW Time,我把重点的信息摘抄和翻译了一下。

背景和动机

今天我们已经在GetGCMemoryInfo 公开了获取GC处理时间和暂停时间的百分比值的API。

具体来说是通过GCMemoryInfoPauseTimePercentage字段。

这个很有用,但是如果我只想要一个分子(即:程序运行以来总的GC暂停时间)。现在没有办法获取到。

API 提案

我建议在System.GC上添加一个下面这样的API:

TimeSpan System.GC.GetTotalPauseDuration()

它会返回GC总的暂停时间。

API 使用

TimeSpan start = System.GC.GetTotalPauseDuration();
// ... Perform some work ...
TimeSpan end= System.GC.GetTotalPauseDuration();
Console.WriteLine(end - start + " was spent pausing in GC");

我看到这个API已经和最新的.NET7预览版一起发布,我们下载最新的.NET7 SDK,然后把项目改成.NET7,来试试这个API,代码如下所示:

using System.Diagnostics.Tracing;  

// 开启GC事件监听
var gc = new GcStwMetricsCollector();
// 创建一些对象
var array = Enumerable.Range(0, 1000).Select(s => (decimal)s).ToArray();
// 手动执行GC
GC.Collect();
Console.WriteLine($"API STW:{GC.GetTotalPauseDuration().TotalMilliseconds}ms");
Console.ReadLine();
// 省略上文中一样的代码

运行结果:

API STW: 0.223ms
Event STW: 0.296ms

API统计的应该会更加准确,我们通过事件来获取多多少少有一点额外的开销,不过误差在可接受的范围内。

总结

上文中提到了两种方式来获取.NET GC STW Time,我们只需要稍加改造,就可以将STW监控的功能加入APM中,如下图表就是本地测试时采集的一些数据。



当然通过EventListener还可以实现更多的APM信息的采集,大家有兴趣也可以研究看看。

本文代码链接Github: https://github.com/InCerryGit/BlogCodes/tree/main/Get-GC-STW-Time

往期文章:

.NET性能优化-推荐使用Collections.Pooled(补充)

.NET性能优化-使用ValueStringBuilder拼接字符串

.NET性能优化-使用结构体替代类

如何获取GC(垃圾回收器)的STW(暂停)时间?的更多相关文章

  1. [Java基础]-- Java GC 垃圾回收器的分类和优缺点

    https://blog.csdn.net/high2011/article/details/80177473?utm_source=blogxgwz2 参考:elasticsearch实战-使用G1 ...

  2. 一篇文章让你了解GC垃圾回收器

    简单了解GC垃圾回收器 了解GC之前我们首先要了解GC是要做什么的?顾名思义回收垃圾,什么是垃圾呢? GC回收的垃圾主要指的是回收堆内存中的垃圾对象. 从根对象出发,所有被引用的对象,都是存活对象 其 ...

  3. .NET GC垃圾回收器

    GC垃圾回收器简介 全名: Garbage Collector 原理: 以应用程序的根(root)为基础,遍历应用程序堆(heap)上动态分配的所有对象,通过识别它们是否被引用来确定哪些对象是已经死亡 ...

  4. GC垃圾回收器

    java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”.jvm解决的两个问题:给对象分配内存以及回收分配给对象的内存.GC:将内存中不再被使用的对象进行回收.GC的作用域是JVM运行时 ...

  5. GC(垃圾回收器)中的算法

    GC的两种判定方法 (1) 引用计数法 给对象添加一个引用计数器,每当引用一次+1,每次失效时-1,当计数器为0时,表示对象就是不可能再被使用的. (2) 可达性分析算法 将“GC Roots”对象作 ...

  6. Java GC系列(3):垃圾回收器种类

    本文由 ImportNew - 好好先生 翻译自 javapapers. 目录 垃圾回收介绍 垃圾回收是如何工作的? 垃圾回收的类别 垃圾回收监视和分析 在这篇教程中我们将学习几种现有的垃圾回收器.在 ...

  7. JVM 专题二十一:垃圾回收(五)垃圾回收器 (二)

    3. 回收器 3.1 Serial回收器:串行回收 3.1.1 概述 Serial收集器是最基本.历史最悠久的垃圾收集器了.JDK1.3之前回收新生代唯一的选择. Serial收集器作为Hotspot ...

  8. JVM七大垃圾回收器下篇G1(Garbage First)

    G1回收器:区域化分代式 既然我们已经有了前面几个强大的GC,为什么还要发布Garbage First (G1)GC?  原因就在于应用程序所应对的业务越来越庞大.复杂,用户越来越多,没有GC就不能保 ...

  9. 【转】Java学习---垃圾回收算法与 JVM 垃圾回收器综述

    [原文]https://www.toutiao.com/i6593931841462338062/ 垃圾回收算法与 JVM 垃圾回收器综述 我们常说的垃圾回收算法可以分为两部分:对象的查找算法与真正的 ...

随机推荐

  1. 进阶实战 css 点击按钮的样式

    1.  html结构 <div class="menu-wrap"> <input type="checkbox" class="t ...

  2. 数据结构 - AVL 树

    简介 基本概念 AVL 树是最早被发明的自平衡的二叉查找树,在 AVL 树中,任意结点的两个子树的高度最大差别为 1,所以它也被称为高度平衡树,其本质仍然是一颗二叉查找树. 结合二叉查找树,AVL 树 ...

  3. No value specified for 'Date' BeanUtils.copyProperties 日期为空 转型错误

    BEGIN; 最近在用spring data,使用的hibernate实现,然后用了一对多等关系配置,导致PO类转换JSON时会死循环,最后使用VO接受数据解决该问题.PO与VO相互转换我用的是org ...

  4. 论文解读(GROC)《Towards Robust Graph Contrastive Learning》

    论文信息 论文标题:Towards Robust Graph Contrastive Learning论文作者:Nikola Jovanović, Zhao Meng, Lukas Faber, Ro ...

  5. Linux和kali Linux 介绍

    常用的渗透测试平台 CTFTools kali (近亲 Ubuntu) Parrot Security OS PentestBox --由印度人开发,运行在Windows下的渗透测试环境 kali L ...

  6. Python学习-Day1(Typora软件与计算机)

    学习总括 Typora软件介绍(markdown语法) 相关拓展知识 文件的后缀名是什么? 什么是语言? 什么是编程语言? 什么是编程?(程序员写代码的本质) 计算机的五大组成部分 计算机的本质 计算 ...

  7. vue2.x版本中Object.defineProperty对象属性监听和关联

    前言 在vue2.x版本官方文档中 深入响应式原理 https://cn.vuejs.org/v2/guide/reactivity.html一文的解释当中,Object.defineProperty ...

  8. 10个最危险的Linux命令,希望你牢记在心

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 来源:Linux迷链接:https://www.linu ...

  9. Mysql数据库基础_复习思维导图

    Mysql复习的一个小总结,用xmind写的.(字数没有都不给我发博客) 下面是一些备注 子查询 MySQL子查询称为内部查询,而包含子查询的查询称为外部查询. 子查询可以在使用表达式的任何地方使用, ...

  10. 面试官:Kafka是什么,它有什么特性与使用场景?

    哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 不知不觉进入了五月份了,天气越 ...