一:背景

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);
}
}
}
}

更多办法参考: https://stackoverflow.com/questions/639540/how-much-memory-can-a-32-bit-process-access-on-a-64-bit-operating-system

三:总结

总的来说,2G 内存限制 是一个 32bit 程序所必须面对的问题,知道了就好解决了,最后有一个问题要解释下,为什么 commit 内存高达 1.6G,这是因为医疗类的软件,大多是 FastReport + DevExpress 这些重量级的经典搭配以及大量的图片资源占用了太多 native memory。

记一次 .NET 某妇产医院 WPF内存溢出分析的更多相关文章

  1. 记一次 .NET 医院CIS系统 内存溢出分析

    一:背景 1. 讲故事 前几天有位朋友加wx求助说他的程序最近总是出现内存溢出,很崩溃,如下图: 和这位朋友聊下来,发现他也是搞医疗的,哈哈,.NET 在医疗方面还是很有市场的,不过对于内存方面出的问 ...

  2. jvm内存溢出分析

    概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...

  3. java内存溢出分析(二)

    我们继续java内存溢出分析(一)的分析,点击Details>按钮,显示如下图,我们发现有一个对象数量达到280370216个,再点击其中的List objects 点击后,显示下图 至此,我们 ...

  4. java内存溢出分析工具

    http://www.cnblogs.com/preftest/archive/2011/12/08/2281322.html java内存溢出分析工具:jmap使用实战 在一次解决系统tomcat老 ...

  5. Java 内存溢出分析

    原文地址:Java 内存溢出分析 博客地址:http://www.moonxy.com 一.前言 Java 的 JVM 的内存一般可分为 3 个区:堆(heap).栈(stack)和方法区(metho ...

  6. jmap的使用以及内存溢出分析

    一.jmap的使用以及内存溢出分析 前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,如:内存使用情况的汇总.对内存溢出的定位与分析 1.查看内存使用情况 jma ...

  7. 记一次线上环境的内存溢出(java.lang.OutOfMemoryError)

    事故背景 今天客户说风控项目有个别用户查询不到数据不是报错就是一直卡在那里,我就去那个接口看了下. 一看项目日志今天的都几个g了,平常也就几百兆吧,很明显出了问题. 请求接口后使用命令tail -f ...

  8. 【Android】Eclipse Memory Analyzer 进行堆内存溢出分析

    MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件.     不同厂家的 JVM 所生成的堆转储文件在数据存储格式以及数据存储内容上有很多区别,但是比较主流的厂家和格式,例如 Sun, HP, ...

  9. Hibernate内存溢出分析一例

    公司业务系统在进行压力测试时,压测24小时后系统发生内存溢出.经过分析读dump文件,发现org.hibernate.stat.StatisticsImpl类的hashmap类型的变量存储了大量数据( ...

随机推荐

  1. mybatis bind 标签

    bind 标签可以使用 OGNL 表达式创建一个变量井将其绑定到上下文中.在前面的例子中, UserMapper.xml 有一个 selectByUser 方法,这个方法用到了 like 查询条件,部 ...

  2. php 数组(2)

    数组排序算法 冒泡排序,是一种计算机科学领域的较简单的排序算法.它重复地访问要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们减缓过来.走访数列的工作室重复的进行直到没有再需要交换,也就是说该 ...

  3. 14-1-Unsupervised Learning ---dimension reduction

    无监督学习(Unsupervised Learning)可以分为两种: 化繁为简 聚类(Clustering) 降维(Dimension Reduction) 无中生有(Generation) 所谓的 ...

  4. 1-Introduction(介绍)

    目录: 正文: (一)機器學習就是让机器自動找函式 语音/图像识别 (二)你想找什麼樣的函式? (2.1)Regression 找出的函数其输出是一个数值 (2.2)Classification (2 ...

  5. 一个初步的lilypond模板

    代码文档在下方,涉及了许多基本的文档操作,包括: 1)页面设置,包括纸张大小.页边距 2)段落设置,包括不同内容之间的行距 3)乐谱设置,包括设置谱子大小.谱号.调号.拍号,甚至还有拍号/小节线不可见 ...

  6. 如何利用 JuiceFS 的性能工具做文件系统分析和调优

    JuiceFS 是一款面向云原生环境设计的高性能 POSIX 文件系统,在 AGPL v3.0 开源协议下发布.作为一个云上的分布式文件系统,任何存入 JuiceFS 的数据都会按照一定规则拆分成数据 ...

  7. JdbcTemplate 、Mybatis、ORM 、Druid 、HikariCP 、Hibernate是什么?它们有什么关系?

    JdbcTemplate .Mybatis.ORM .Druid .HikariCP .Hibernate是什么?它们有什么关系? 学完Spring和SpringMVC之后,就急于求成的开始学习起Sp ...

  8. 深入理解Redis 数据结构—简单动态字符串sds

    Redis是用ANSI C语言编写的,它是一个高性能的key-value数据库,它可以作用在数据库.缓存和消息中间件.其中 Redis 键值对中的键都是 string 类型,而键值对中的值也是有 st ...

  9. Atcoder Grand Contest 021 F - Trinity(dp+NTT)

    Atcoder 题面传送门 & 洛谷题面传送门 首先我们考虑设 \(dp_{i,j}\) 表示对于一个 \(i\times j\) 的网格,其每行都至少有一个黑格的合法的三元组 \((A,B, ...

  10. Docker Nginx-Proxy 容器Nginx Proxy反向代理

    Docker Nginx-Proxy 容器Nginx Proxy反向代理   简单介绍 Docker容器的自动Nginx反向代理   dockerhub地址 https://hub.docker.co ...