当前位置: 首页 > news >正文

如何让.NET应用使用更大的内存

我一直在思考为何Redis这种应用就能独占那么大的内存空间而我开发的应用为何只有4GB大小左右,在此基础上也问了一些大佬,最终还是验证下自己的猜测。

操作系统限制

主要为32位操作系统和64位操作系统。

每个进程自身还分为了用户进程空间和内核进程空间,基本上各一半,而应用本身主要的空间就是用户进程空间。

32位操作系统寻址长度

寻址总线宽度32位,2^32次方,也就是4GB 大小

那么,用户态空间(用户空间 只有2G)

64位操作系统寻址长度

寻址总线实际总线宽度48位,2^48次方,也就是256TB 大小

操作系统本身的限制(Windows)

以上说的是32位进程的用户模式虚拟地址空间为2GB,在32位系统上可以打开3GB开关或者采用4GT技术后,最多能达到3GB的用户空间,在64位系统上默认是打开的,最多分配4GB的虚拟用户模式内存。

而在64位进程的用户模拟虚拟空间,在32位上不适用,而在64位系统中默认开启了这个功能,并且能达到8TB以上的虚拟内存。

这个图说的是实际上在这个操作系统下,真实的物理内存 在86,也就是32位下最多只有4GB 物理内存的支持,那为啥我们看到实际上win7 32位也支持很大的内存条,那是因为开启了 物理地址扩展 PAE 功能,而应用自身的寻址空间是不变的。


可以明显感觉到 Win11 比 Win10 能支持的物理内存更大

服务器版本的操作系统支持的更更大,当然,也没有得到 物理系统本身的极限 256TB。

也说明了实际的物理内存,服务器版本会支持更大的物理内存。

.NET 应用自身的限制

.NET 这边因为有CLR的存在,把内存又分为了托管内存和非托管内存,而用户态空间,也就是用户空间实际上就是托管内存空间,它的大小实际上是限制住的。

所以实际上,托管数组的长度限制在0x7FFFFFC7了,官方的说法是为了防止溢出(《.NET 运行时 最大长度限制》)。

Retrieved 280000000 items limit:2147483591 out:False 0GB个 of data .2 GB

大概意思就是,创建了 280000000的随机数,double类型的,数组的极限是0x7FFFFFC7( 2147483591),是否超出了这个极限,大概有多少GB条数据,一共占用多少GB空间。

可以看到最后一条数据
一共创建了 2140000000条,距离极限相差 7,483,591条,基本证明,这个限制是存在的。
实际上,它一共占用了14GB 内存(大概,实际上波动还挺大)

public static void Test0()
{Double[] values = GetData();// Compute mean.Console.WriteLine("Sample mean: {0}, N = {1}",GetMean(values), values.Length);static Double[] GetData(){var d = 0x7FFFFFC7;Random rnd = new Random();List<Double> values = new List<Double>();for (int ctr = 1; ctr <= int.MaxValue; ctr++){values.Add(rnd.NextDouble());if (ctr % 10000000 == 0){var memSize = ((long)values.Count * 8) / 1024 / 1024 / 1024;Console.WriteLine($"Retrieved {ctr} items limit:{d} out:{ctr >= d} {(long)values.Count / 1024 / 1024 / 1024}GB个 of data .{memSize} GB");}}return values.ToArray();}static Double GetMean(Double[] values){Double sum = 0;foreach (var value in values)sum += value;return sum / values.Length;}
}

这是64位应用自身可以操作大内存的验证。

而32位应用只操作了6千万条数据就内存溢出了,如下图。

非托管内存申请大内存

public static void Test2()
{var list = new List<IntPtr>();try{for (int i = 0; i < 8; i++){var ptr = Marshal.AllocHGlobal(int.MaxValue);//默认最大2G申请,单个方法list.Add(ptr);for (int j = 0; j < int.MaxValue; j++){Marshal.WriteByte(ptr, j, (byte)(66 + i));}Console.WriteLine($"写入成功{i}");}Console.WriteLine("申请完成");Console.ReadLine();}catch (Exception ex){Console.WriteLine(ex.Message);}finally{foreach (var item in list){Marshal.FreeHGlobal(item);}}
}

64位应用

写入全部成功

内存占用也基本占满了整个内存,剩余的16GB。

32位应用

而32位应用程序,直接内存就溢出了。

所以也证明,非托管资源跟32位进程寻址空间是有关系的。

大内存应用的方案

大内存应该是大于4G内存的才叫大内存。

基本上就不太考虑32位应用了。毕竟32位应用的寻址空间太过受限,尽量采用64位应用开发,可以使用托管资源实现大内存应用和非托管内存实现大应用。

MemoryMappedFiles (内存文件映射方案)

这个方案的好处是,虽然应用空间最小2GB,但是,可以在这2GB空间里实现视窗寻址文件,实现另外一种大内存的方案。
也不受限于应用的地址位数(86,64)。

Marshal.AllocHGlobal (非托管资管)

用这个的话,感觉回到了C语言时代,需要自己管理资源的申请与释放,另外,只有64位系统才会有更多的内存申请。

64位应用

在托管资源下,64位应用本身的空间已经能占用很大的空间,足够进行大内存应用的开发。也建议使用这种方式。

多进程

另外一种简单的方案就是采用多进程的方式实现多占内存资源。

代码地址

https://github.com/kesshei/MemeryTest.git

https://gitee.com/kesshei/MemeryTest.git

总结

一直在思考大内存的应用,如何申请大的内存,只有实际测试和验证才知道有哪种以及哪种的方式是最佳的。
现在才明白,Redis 64位系统不限制内存,32位系统最多使用3GB内存。所以,如果你想开发一个类Redis这种的中间件,内存的限制就这么多。

参考资料地址

《Windows 和 Windows Server 版本的内存限制》
https://learn.microsoft.com/zh-cn/windows/win32/memory/memory-limits-for-windows-releases?redirectedfrom=MSDN
《What Is 4GT? 什么是4GT?》
https://learn.microsoft.com/zh-cn/previous-versions/windows/it-pro/windows-server-2003/cc786709(v=ws.10)
《物理地址扩展 PAE》
https://learn.microsoft.com/zh-cn/windows/win32/memory/physical-address-extension
《.NET 运行时 最大长度限制》
https://github.com/dotnet/runtime/blob/f107b63fca1bd617a106e3cc7e86b337151bff79/src/coreclr/vm/gchelpers.cpp#L350

一键三连呦!,感谢大佬的支持,您的支持就是我的动力!

相关文章:

  • 数据结构:图文详解 队列 | 循环队列 的各种操作(出队,入队,获取队列元素,判断队列状态)
  • 计算机网络2
  • Excel怎样统计一列中不同的数据分别有多少个?
  • 【影像组学入门百问】#29---#31
  • 云服务器初次连接宝塔,连接不上
  • 基于QTreeWidget实现多级组织结构
  • 设计模式详解---模板方法模式
  • Python爬虫之两种urlencode编码发起post请求方式
  • Qt-QTransform介绍与使用
  • 机器学习算法---回归
  • Linux Java Jar Shell 脚本
  • 使用 React 实现自定义数据展示日历组件
  • 《Linux C编程实战》笔记:进程操作之退出,执行,等待
  • SQL进阶理论篇(十二):InnoDB中的MVCC是如何实现的?
  • 基于点云去滤除灰尘的滤波算法调研
  • 分享的文章《人生如棋》
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • Android开源项目规范总结
  • Angular 响应式表单 基础例子
  • bootstrap创建登录注册页面
  • CSS 三角实现
  • Git同步原始仓库到Fork仓库中
  • JavaScript的使用你知道几种?(上)
  • JAVA多线程机制解析-volatilesynchronized
  • js中的正则表达式入门
  • Laravel核心解读--Facades
  • Less 日常用法
  • MySQL几个简单SQL的优化
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • node-glob通配符
  • PAT A1092
  • Protobuf3语言指南
  • Python进阶细节
  • scala基础语法(二)
  • Service Worker
  • unity如何实现一个固定宽度的orthagraphic相机
  • Vim Clutch | 面向脚踏板编程……
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 半理解系列--Promise的进化史
  • 第十八天-企业应用架构模式-基本模式
  • 如何实现 font-size 的响应式
  • 使用 @font-face
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 用jQuery怎么做到前后端分离
  • 从如何停掉 Promise 链说起
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • ​Spring Boot 分片上传文件
  • ​如何在iOS手机上查看应用日志
  • #、%和$符号在OGNL表达式中经常出现
  • (10)STL算法之搜索(二) 二分查找
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (附源码)php投票系统 毕业设计 121500
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (九)信息融合方式简介