【发布时间】:2026-02-11 07:05:03
【问题描述】:
不确定对于 Java、C# 和 C++ 的答案是否相同,所以我对它们进行了分类。所有语言的答案都会很好。
我整天都在想,如果我分配数组,所有单元格都会在一个连续的空间中。因此,如果系统中的一块内存不足,则会引发内存不足的异常。
没关系,我说的是什么?或者有没有可能,分配的数组会被分页?
【问题讨论】:
标签: c# java c++ memory-management
不确定对于 Java、C# 和 C++ 的答案是否相同,所以我对它们进行了分类。所有语言的答案都会很好。
我整天都在想,如果我分配数组,所有单元格都会在一个连续的空间中。因此,如果系统中的一块内存不足,则会引发内存不足的异常。
没关系,我说的是什么?或者有没有可能,分配的数组会被分页?
【问题讨论】:
标签: c# java c++ memory-management
C++ 数组是连续的,这意味着内存具有连续的地址,即它在虚拟地址空间中是连续的。它在物理地址空间中不需要是连续的,因为现代处理器(或其内存子系统)具有将虚拟页面与物理页面相关联的大映射。在用户模式下运行的进程永远不会看到其阵列的物理地址。
我认为实际上大多数或所有 Java 实现都是相同的。但是程序员永远不会看到数组元素的实际地址,只是对数组的引用和索引它的方法。因此,理论上,Java 实现可以破坏数组并将该事实隐藏在 [] 运算符中,尽管 JNI 代码仍然可以以 C++ 样式查看数组,此时需要一个连续的块。这是假设 JVM 规范中没有关于数组布局的任何内容,而 jarnbjo 告诉我没有。
我不了解 C#,但我预计情况与 Java 非常相似 - 您可以想象一个实现可能会使用 [] 运算符来隐藏数组在虚拟地址空间中不连续的事实。一旦有人获得指向它的指针,这种伪装就会失败。 [编辑:多项式表示 C# 中的数组在有人固定它们之前可能是不连续的,这是有道理的,因为您知道必须先固定对象,然后才能将它们传递给使用地址的低级代码。]
请注意,如果您分配某个大型对象类型的数组,那么在 C++ 中,该数组实际上是许多端到端放置的大型结构,因此所需的连续分配大小取决于对象的大小。在 Java 中,对象数组“实际上”是引用数组。所以这是一个比 C++ 数组更小的连续块。对于原生类型,它们是相同的。
【讨论】:
GetWhateverArrayElements 时,实现才需要为数组找到一个连续的地址范围。答案已更新,谢谢。
在 C# 中,您不能保证内存块是连续的。 CLR 试图在一个连续的块中分配内存,但它可能会在几个块中分配它。关于 CLR 应如何管理 C# 内存的定义行为很少,因为它被设计为由托管构造抽象出来。
在 C# 中真正重要的唯一情况是,如果您通过 P/Invoke 将数组作为指针传递给一些非托管代码,在这种情况下,您应该使用 GC.Pin 锁定对象在内存中的位置。也许其他人将能够解释 CLR 和 GC 在这种情况下如何处理对连续内存的需求。
【讨论】:
Buffer.BlockCopy()),那么 .NET 中有太多内容会简单地中断。此外,如果 .NET 可以将大型数组分配为一些较小分配的集合,则不需要大型对象堆。
没关系,我说的是什么?
没错,在 Java 和 C# 中,但 C++ 只有在达到进程或系统限制时才会出错。不同之处在于,在 Java 和 C# 中,应用程序对其自身施加了限制。在 C++ 中,限制是由操作系统强加的。
或者有没有可能,分配的数组会被分页?
这也是可能的。但是在 Java 中,堆分页对性能非常不利。当 GC 运行时,所有检查的对象都必须在内存中。在 C++ 中它不是很好,但影响较小。
如果您想要可以在 Java 中分页的大型结构,您可以使用 ByteBuffer.allocateDirect() 或内存映射文件。这通过使用堆外内存来工作(基本上是 C++ 使用的)
【讨论】:
在 C(++) 程序中,通常(也就是说,除非我们谈论的是解释代码而不是直接编译 + 执行它)数组在虚拟地址空间中是连续的(当然,如果存在这样的有问题的平台上的东西)。
在那里,如果一个大数组不能连续分配,即使有足够的空闲内存,你也会得到 std::bad_alloc 异常(在 C++ 中)或 NULL(来自 C/中的 malloc()-like 函数C++ 或 C++ 中的非抛出运算符 new)。
虚拟内存(和磁盘分页)通常不能解决虚拟地址空间碎片问题,或者至少不能直接解决,它的用途不同。它通常用于让程序认为有足够的内存,而实际上没有。可用磁盘空间有效地扩展了 RAM,但以降低性能为代价,因为当存在内存压力时,操作系统必须在 RAM 和磁盘之间交换数据。
操作系统可以将您的阵列(部分或全部)卸载到磁盘上。但这对您的程序来说是透明的,因为每当它需要访问数组中的某些内容时,操作系统都会将其加载回来(再次,部分或全部,因为操作系统认为有必要)。
在没有虚拟内存的系统上,没有虚拟到物理地址的转换,您的程序将直接使用物理内存,因此,它必须处理物理内存碎片并与其他程序竞争空闲内存和地址空间,通常更容易发生分配失败(具有虚拟内存的系统通常在单独的虚拟地址空间中运行程序,应用 A 的虚拟地址空间中的碎片不会影响应用 B 的碎片)。
【讨论】:
当然是 Java 和 C#。我们可以通过在内存页面大小为 4096 字节的 Windows 机器上运行 byte[] array = new byte[4097]; 来展示这一点。因此它必须在一页以上。
当然分页会影响性能,但这可能是使用 .NET 或 Java 等框架的 GC 可能具有优势的情况之一,因为 GC 是由知道分页发生的人编写的。结构仍然有一些优势,使得它更有可能在同一页面上具有相关元素(偏爱数组支持的集合而不是指针追踪的集合)。这在 CPU 缓存方面也有优势。 (大数组仍然是导致 GC 必须解决的堆碎片的最佳方法之一,但由于 GC 非常擅长这样做,它仍然会胜过许多其他处理相同问题的方法)。
几乎可以肯定的是,使用 C++,因为我们通常在操作系统的内存管理级别进行编码 - 数组位于连续的虚拟空间(无论是在堆上还是在堆栈上),而不是连续的物理空间。在 C 或 C++ 中可以编写低于此级别的代码,但这通常只能由实际编写内存管理代码本身的人来完成。
【讨论】:
如果 java Array 被实现为 Object....
并且对象仅在堆中获取 m/m...
所以我不完全确定,但 ..heap 仅在 RAM 中制作....
你可以检查..IBM M/m
【讨论】: