如何仅通过应用代码检测.NET Framework ASP.NET运行时大内存对象
.NET Framework ASP.NET 共享IIS环境纯代码内存排查方案
核心前提说明
.NET Framework 未公开允许直接遍历所有托管堆对象的托管API,你预想的直接返回所有大对象列表的GetLargeObjects方法无法通过纯托管代码实现,以下是在你的限制条件下可落地的排查方案。
适配限制的排查思路
你所处的环境无法安装工具、无法远程调试、只能通过应用自身代码排查,优先针对大对象堆(LOH)和静态引用泄漏两个最高发的问题点排查:
- 大于85000字节的对象默认都会进入大对象堆(LOH),可通过进程内性能计数器获取LOH总大小,无需额外权限
- 绝大多数ASP.NET内存泄漏都是静态变量持有大量对象未释放导致,可直接遍历当前AppDomain所有静态字段,筛选出持有的大对象
- 针对业务中可能生成大对象的位置(大数据查询、文件处理、接口响应序列化等),可加入对象大小统计逻辑,超过阈值就记录类型、调用栈、大小信息
完整MemoryInspector实现代码
Imports System Imports System.Collections.Generic Imports System.Diagnostics Imports System.Reflection Imports System.Runtime.InteropServices Class MemoryInspector ' 原有方法保留 Public Shared Function GetMemoryUsed() As Long Return System.GC.GetTotalMemory(False) End Function Public Shared Sub Collect() System.GC.Collect() System.GC.WaitForPendingFinalizers() System.GC.Collect() End Sub ' 获取大对象堆总大小 Public Shared Function GetLOHTotalSize() As Long Try Using pc As New PerformanceCounter(".NET CLR Memory", "Large Object Heap size", Process.GetCurrentProcess().ProcessName, True) Return CLng(pc.NextValue()) End Using Catch Return -1 ' 无权限访问性能计数器时返回-1 End Try End Function ' 列出静态字段持有的所有大对象(替代原需求的GetLargeObjects) Public Shared Function GetLargeStaticObjects() As List(Of (TypeName As String, FieldName As String, Size As Long)) Dim result As New List(Of (String, String, Long)) For Each assem In AppDomain.CurrentDomain.GetAssemblies() Try ' 可选过滤:只扫描你自己的业务程序集,降低性能开销 ' If Not assem.FullName.Contains("你的业务程序集名前缀") Then Continue For For Each type In assem.GetTypes() Dim staticFields = type.GetFields(BindingFlags.NonPublic Or BindingFlags.Public Or BindingFlags.Static) For Each field In staticFields Dim value = field.GetValue(Nothing) If value IsNot Nothing Then Dim size = CalculateObjectSize(value, New HashSet(Of Object)()) If size > 85000 Then ' 只筛选大对象阈值以上的对象 result.Add((type.FullName, field.Name, size)) End If End If Next Next Catch ' 忽略无权限访问的程序集/类型 End Try Next Return result End Function ' 递归计算对象大小,带循环引用防护,返回近似值足够定位问题 Private Shared Function CalculateObjectSize(obj As Object, visited As HashSet(Of Object)) As Long If obj Is Nothing OrElse visited.Contains(obj) Then Return 0 visited.Add(obj) Dim type = obj.GetType() If type.IsValueType Then Return Marshal.SizeOf(obj) End If If TypeOf obj Is String Then Return Len(CStr(obj)) * 2 + 24 End If If type.IsArray Then Dim arr As Array = CType(obj, Array) If arr.Length = 0 Then Return 24 Dim elementType = type.GetElementType() Dim elementSize = If(elementType.IsValueType, Marshal.SizeOf(elementType), IntPtr.Size) Dim totalSize As Long = 24 + arr.Length * elementSize If Not elementType.IsValueType Then For Each item In arr totalSize += CalculateObjectSize(item, visited) Next End If Return totalSize End If Dim size As Long = IntPtr.Size * 2 Dim fields = type.GetFields(BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic) For Each field In fields If field.FieldType.IsValueType Then size += Marshal.SizeOf(field.FieldType) Else size += CalculateObjectSize(field.GetValue(obj), visited) End If Next Return size End Function End Class
快速使用方法
新增一个受密码保护的测试页面,页面加载时调用以下逻辑输出结果,直接通过浏览器即可查看内存统计:
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load ' 简单密码校验,避免被外部未授权访问 If Request("pwd") <> "你自定义的校验密码" Then Response.End() Return End If ' 强制GC回收一次获取更准确的统计值 MemoryInspector.Collect() Response.Write($"总内存占用:{MemoryInspector.GetMemoryUsed() / 1024 / 1024:0.00} MB<br/>") Response.Write($"大对象堆总占用:{MemoryInspector.GetLOHTotalSize() / 1024 / 1024:0.00} MB<br/>") Response.Write("<h3>静态字段持有的大对象列表</h3>") Response.Write("<table border='1'><tr><th>类型名</th><th>字段名</th><th>占用大小(MB)</th></tr>") For Each item In MemoryInspector.GetLargeStaticObjects().OrderByDescending(Function(x) x.Size) Response.Write($"<tr><td>{item.TypeName}</td><td>{item.FieldName}</td><td>{item.Size / 1024 / 1024:0.00}</td></tr>") Next Response.Write("</table>") End Sub
注意事项
- 对象大小计算为近似值,足够定位大对象位置,不需要完全精确
- 排查完成后记得删除测试页面和诊断代码,避免线上性能损耗
内容的提问来源于stack exchange,提问作者Wolfgang Amadeus
相关产品推荐
相关产品推荐

