记一次 .NET 某妇产医院 WPF内存溢出分析
一:背景
1. 讲故事
上个月有位朋友通过博客园的短消息找到我,说他的程序存在内存溢出情况,寻求如何解决。
要解决还得通过 windbg 分析啦。
二:Windbg 分析
1. 为什么会内存溢出
大家都知道内存溢出对应着 .NET 中的 OutOfMemoryException 异常,这种异常有可能是托管代码手工抛出的,也有可能是CLR层面抛出的,言外之意就是可以通过两种方式排查。
- 托管线程是否挂载着异常?
0:000> !t
ThreadCount: 23
UnstartedThread: 0
BackgroundThread: 5
PendingThread: 0
DeadThread: 17
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 362c 00fac868 26020 Preemptive 7ED701A0:00000000 00fa6b60 0 STA
5 2 2d70 00fbeba0 2b220 Preemptive 7EBA7AC0:00000000 00fa6b60 0 MTA (Finalizer)
7 3 3264 061c8890 102a220 Preemptive 00000000:00000000 00fa6b60 0 MTA (Threadpool Worker)
17 15 3f98 19682b90 202b220 Preemptive 7EBB0830:00000000 00fa6b60 0 MTA
XXXX 16 0 2845fb00 35820 Preemptive 00000000:00000000 00fa6b60 0 Ukn
18 14 a7c 2842b1c8 202b220 Preemptive 00000000:00000000 00fa6b60 0 MTA
XXXX 6 0 2c9b3778 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 18 0 288a1318 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 23 0 288a22f0 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 10 0 2ccf3550 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 21 0 288a1860 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 12 0 288a1da8 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 11 0 2c993640 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 8 0 2ccf3a98 35820 Preemptive 00000000:00000000 00fa6b60 0 Ukn
XXXX 9 0 2ccf2030 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 7 0 2c9aed88 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 26 0 28898308 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 25 0 2c492c68 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 4 0 2c993b88 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 20 0 2c9af2d0 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 17 0 2c9afd60 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
XXXX 24 0 2c9b1280 1039820 Preemptive 00000000:00000000 00fa6b60 0 Ukn (Threadpool Worker)
23 22 2658 2c9b02a8 1029220 Preemptive 7ED5BFF8:00000000 00fa6b60 0 MTA (Threadpool Worker)
从输出信息看,这些线程并没有挂载任何托管异常,我去。。。
- 是否在 CLR 上抛出
这主要是看 托管堆(heap) 上的内存分配或者gc回收造成的内存不足,可以用 !ao 命令。
0:000> !ao
There was no managed OOM due to allocations on the GC heap
从输出信息看也没有任何异常,尴尬了。。。 尼玛,那到底是因为什么呢?
2. 探索溢出原因
出现这种尴尬情况,我只能怀疑生成这个dump的时候并没有get到那个点,或者是我的知识边界有限,不过天无绝人之路,不在那个 点 也肯定在那个 点 附近,对吧,接下来用 !address -summary 看一下内存使用的归类信息。
0:000> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unknown> 1520 4c185000 ( 1.189 GB) 65.57% 59.45%
Image 4306 1f140000 ( 497.250 MB) 26.78% 24.28%
Free 1133 bf17000 ( 191.090 MB) 9.33%
Heap 617 7626000 ( 118.148 MB) 6.36% 5.77%
Stack 72 1740000 ( 23.250 MB) 1.25% 1.14%
Other 34 7b000 ( 492.000 kB) 0.03% 0.02%
TEB 24 30000 ( 192.000 kB) 0.01% 0.01%
PEB 1 3000 ( 12.000 kB) 0.00% 0.00%
--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_MAPPED 549 34b60000 ( 843.375 MB) 45.42% 41.18%
MEM_PRIVATE 1718 20424000 ( 516.141 MB) 27.80% 25.20%
MEM_IMAGE 4307 1f155000 ( 497.332 MB) 26.78% 24.28%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT 4904 66ddd000 ( 1.607 GB) 88.64% 80.37%
MEM_RESERVE 1670 d2fc000 ( 210.984 MB) 11.36% 10.30%
MEM_FREE 1133 bf17000 ( 191.090 MB) 9.33%
--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READONLY 2272 382cf000 ( 898.809 MB) 48.41% 43.89%
PAGE_READWRITE 1572 1eead000 ( 494.676 MB) 26.64% 24.15%
PAGE_EXECUTE_READ 218 dd59000 ( 221.348 MB) 11.92% 10.81%
PAGE_WRITECOPY 449 133e000 ( 19.242 MB) 1.04% 0.94%
PAGE_EXECUTE_READWRITE 188 ab4000 ( 10.703 MB) 0.58% 0.52%
PAGE_NOACCESS 156 9c000 ( 624.000 kB) 0.03% 0.03%
PAGE_READWRITE | PAGE_GUARD 48 78000 ( 480.000 kB) 0.03% 0.02%
PAGE_READWRITE | PAGE_WRITECOMBINE 1 2000 ( 8.000 kB) 0.00% 0.00%
--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
<unknown> 1d200000 a001000 ( 160.004 MB)
Image fed1000 36e4000 ( 54.891 MB)
Free 33dfe000 1082000 ( 16.508 MB)
Heap 3da84000 a1b000 ( 10.105 MB)
Stack 1a10000 fd000 (1012.000 kB)
Other 7fa40000 33000 ( 204.000 kB)
TEB a4c000 3000 ( 12.000 kB)
PEB a3d000 3000 ( 12.000 kB)
从上面的 MEM_COMMIT=1.607 GB 80.37% 信息看,当前内存占用 1.6G,占比 80.37%,可以看出它受到了一个 2G内存 的限制,而且从 !t 输出中的内存地址看,当前是 32bit 程序,所以这是一个经典的: 64系统跑着32位程序被2G内存限制 的问题。
3. 如何突破 2G 限制
要寻找答案,还得看最权威的 MSDN: https://docs.microsoft.com/en-us/windows/win32/memory/memory-limits-for-windows-releases?redirectedfrom=MSDN
破局 还得设置程序的 IMAGE_FILE_LARGE_ADDRESS_AWARE 标记。
关于具体怎么设置,我找了三种方法。
- 使用 LargeAddressAware 安装包
参见 github: https://github.com/KirillOsenkov/LargeAddressAware
- 使用 editbin
可以在 vs 的生成事件中输入 editbin /largeaddressaware $(TargetPath)。
- 使用代码方式
这种可以直接给生成好的 exe 增加 LargeAddressAware 标记,除了标记,还能检测,
using System;
using System.IO;
namespace PEFile
{
public class LargeAddressAware
{
public static bool IsLargeAddressAware(string filePath)
{
bool isLargeAddressAware = false;
PrepareStream(filePath, (stream, binaryReader) => isLargeAddressAware = (binaryReader.ReadInt16() & 0x20) != 0);
return isLargeAddressAware;
}
public static void SetLargeAddressAware(string filePath)
{
PrepareStream(filePath, (stream, binaryReader) =>
{
var value = binaryReader.ReadInt16();
if ((value & 0x20) == 0)
{
value = (short)(value | 0x20);
stream.Position -= 2;
var binaryWriter = new BinaryWriter(stream);
binaryWriter.Write(value);
binaryWriter.Flush();
}
});
}
private static void PrepareStream(string filePath, Action<Stream, BinaryReader> action)
{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
{
if (stream.Length < 0x3C)
{
return;
}
var binaryReader = new BinaryReader(stream);
// MZ header
if (binaryReader.ReadInt16() != 0x5A4D)
{
return;
}
stream.Position = 0x3C;
var peHeaderLocation = binaryReader.ReadInt32();
stream.Position = peHeaderLocation;
// PE header
if (binaryReader.ReadInt32() != 0x4550)
{
return;
}
stream.Position += 0x12;
action(stream, binaryReader);
}
}
}
}
三:总结
总的来说,2G 内存限制 是一个 32bit 程序所必须面对的问题,知道了就好解决了,最后有一个问题要解释下,为什么 commit 内存高达 1.6G,这是因为医疗类的软件,大多是 FastReport + DevExpress 这些重量级的经典搭配以及大量的图片资源占用了太多 native memory。

记一次 .NET 某妇产医院 WPF内存溢出分析的更多相关文章
- 记一次 .NET 医院CIS系统 内存溢出分析
一:背景 1. 讲故事 前几天有位朋友加wx求助说他的程序最近总是出现内存溢出,很崩溃,如下图: 和这位朋友聊下来,发现他也是搞医疗的,哈哈,.NET 在医疗方面还是很有市场的,不过对于内存方面出的问 ...
- jvm内存溢出分析
概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...
- java内存溢出分析(二)
我们继续java内存溢出分析(一)的分析,点击Details>按钮,显示如下图,我们发现有一个对象数量达到280370216个,再点击其中的List objects 点击后,显示下图 至此,我们 ...
- java内存溢出分析工具
http://www.cnblogs.com/preftest/archive/2011/12/08/2281322.html java内存溢出分析工具:jmap使用实战 在一次解决系统tomcat老 ...
- Java 内存溢出分析
原文地址:Java 内存溢出分析 博客地址:http://www.moonxy.com 一.前言 Java 的 JVM 的内存一般可分为 3 个区:堆(heap).栈(stack)和方法区(metho ...
- jmap的使用以及内存溢出分析
一.jmap的使用以及内存溢出分析 前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,如:内存使用情况的汇总.对内存溢出的定位与分析 1.查看内存使用情况 jma ...
- 记一次线上环境的内存溢出(java.lang.OutOfMemoryError)
事故背景 今天客户说风控项目有个别用户查询不到数据不是报错就是一直卡在那里,我就去那个接口看了下. 一看项目日志今天的都几个g了,平常也就几百兆吧,很明显出了问题. 请求接口后使用命令tail -f ...
- 【Android】Eclipse Memory Analyzer 进行堆内存溢出分析
MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件. 不同厂家的 JVM 所生成的堆转储文件在数据存储格式以及数据存储内容上有很多区别,但是比较主流的厂家和格式,例如 Sun, HP, ...
- Hibernate内存溢出分析一例
公司业务系统在进行压力测试时,压测24小时后系统发生内存溢出.经过分析读dump文件,发现org.hibernate.stat.StatisticsImpl类的hashmap类型的变量存储了大量数据( ...
随机推荐
- spring定时任务ThreadPoolTaskScheduler使用注意事项之线程池大小
背景 最近小伙伴解决了一个工单,描述为"手工推送案件无法推,提示token失效",当前工单状态为待关闭,解决方案为"东软接口不稳定造成的,东软的接口恢复正常后,问题解决& ...
- windows 上搭建 sftp 服务器 -freesshd全过程( 在linux上部署逐浪CMS的必读教程)
文章标题: windows 上搭建 sftp 服务器 - freesshd全过程 关键字 : freesshd 文章分类: 教程 创建时间: 2020年3月23日 缘由 动手 第一步:添加用户 第二步 ...
- 大爽Python入门教程 2-5 *拓展实践,对比与思考
大爽Python入门公开课教案 点击查看教程总目录 本文偏难. 推荐等第一二三四章上完后,回过来拓展阅读. 基础情景思考 假设有这样一张成绩表 最左边的一列是名字,起名麻烦. 这里直接用ABC...来 ...
- Go iota 原理和源码剖析
iota 是 Go 语言的一个保留字,用作常量计数器.由于 iota 具有自增特性,所以可以简化数字增长的常量定义. iota 是一个具有魔法的关键字,往往令初学者难以理解其原理和使用方法. 本文会从 ...
- python openpyxl、RESTful、Webservice接口 基础知识
最近 在做接口测试的时候,遇到如下问题:如何通过数据驱动去做批量接口测试呢,我们的测试数据放在哪里去维护?下面整理出相关点,供大家参考 1.如何维护接口测试数据:放在excel文件中,通过python ...
- [luogu3292]幸运数字
考虑点分治,将询问离线后计算重心到每一个点的线性基,然后再询问重心到每一个点的线性基,时间复杂度为$o(3600q)$,可以过(然而太菜的我写了倍增维护线性基,震惊于倍增和线性基常数之小) 1 #in ...
- 【k8s】在AWS EKS部署并通过ALB访问k8s Dashboard保姆级教程
本教程适用范围 在AWS上使用EKS服务部署k8s Dashboard,并通过ALB访问 EKS集群计算节点采用托管EC2,并使用启动模板. 使用AWS海外账号,us-west-2区域 使用账号默认v ...
- 论文翻译:2020_Densely connected neural network with dilated convolutions for real-time speech enhancement in the time domain
提出了模型和损失函数 论文名称:扩展卷积密集连接神经网络用于时域实时语音增强 论文代码:https://github.com/ashutosh620/DDAEC 引用:Pandey A, Wang D ...
- spring security 认证源码跟踪
spring security 认证源码跟踪 在跟踪认证源码之前,我们先根据官网说明一下security的内部原理,主要是依据一系列的filter来实现,大家可以根据https://docs.sp ...
- 【Pathview web】通路映射可视化
前言 pathview是一个通路可视化友好的R包,最主要的是它支持多组学数据映射(基因/蛋白-代谢).自己用过它的R包,后来发现有网页版的,果断介绍给学员.因为不常用,记录要点,以后温习备用. 目前w ...