You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何仅通过应用代码检测.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 04:15:00