1

我的目标是确保在 java 中分配的数组是在连续的物理内存中分配的。我遇到的问题是,分配数组的页面在物理内存中往往不是连续的,除非我分配了一个非常大的数组。

我的问题是:

  • 为什么一个非常大的数组可以确保物理内存中的页面是连续的?
  • 有什么方法可以确保在物理内存中分配数组,而不涉及使数组非常大?
  • 如何在不测量缓存命中/缓存未命中的情况下判断 Java 对象/数组存在于哪个页面或物理地址?

我不是在寻找答案,询问我为什么在 java 中这样做。我知道 C 会“解决我的问题”,而且我违背了 java 的基本性质。尽管如此,我有充分的理由这样做。

不需要保证答案一直有效。我正在寻找大部分时间都有效的答案。没有任何合理的 Java 程序员会写出创造性的、开箱即用的答案的额外分数。特定于平台(x86 32 位 64 位)是可以的。

4

6 回答 6

6

不可以。物理上连续的内存需要与操作系统直接交互。大多数应用程序(包括 JVM)只能获得几乎连续的地址。JVM 无法为您提供它无法从操作系统获得的东西。

此外,你为什么要它?如果您正在设置 DMA 传输,那么您可能正在使用除 Java 之外的技术。

一点背景:

现代 PC 中的物理内存通常是一个灵活的数量,位于可更换的 DIMM 模块上。它的每个字节都有一个物理地址,因此操作系统在引导期间确定哪些物理地址可用。事实证明,应用程序最好不直接使用这些地址。相反,所有现代 CPU(及其缓存)都使用虚拟地址。有一个到物理地址的映射表,但这不需要完整 - 通过使用未映射到物理地址的虚拟地址启用交换到磁盘。每个进程有一个表,映射不完整,从而获得了另一个级别的灵活性。如果进程 A 有一个映射到物理地址 X 的虚拟地址,而进程 B 没有,那么进程 B 就无法写入物理地址 X,我们可以认为该内存是进程 A 独有的。

映射表在页面级别工作。页或物理地址的连续子集映射到虚拟地址的连续子集。开销和粒度之间的权衡导致 4KB 页面成为常见的页面大小。但是由于每个页面都有自己的映射,因此不能假设超出该页面大小的连续性。特别是,当页面从物理内存中被逐出,交换到磁盘并恢复时,很有可能最终到达一个新的物理内存地址。程序不会注意到,因为虚拟地址不会改变,只有操作系统管理的映射表会改变。

于 2009-05-06T15:15:49.620 回答
5

鉴于垃圾收集器在(逻辑)内存中移动对象,我认为您将不走运。

你能做的最好的事情就是使用ByteBuffer.allocateDirect。这(通常)不会被 GC 移动到(逻辑)内存中,但它可能会被移动到物理内存中,甚至可以分页到磁盘。如果您想要任何更好的保证,则必须使用操作系统。

话虽如此,如果您可以将页面大小设置为与您的堆一样大,那么所有数组都必然是物理上连续的(或换出)。

于 2009-05-06T15:35:38.417 回答
2

可能有一些方法可以诱使特定的 JVM 执行您想要的操作,但这些方法可能很脆弱、复杂,并且很可能非常特定于 JVM、它的版本、它运行的操作系统等。换句话说,浪费了精力。

因此,在不了解您的问题的情况下,我认为没有人能够提供帮助。一般来说,在 Java 中肯定没有办法做到这一点,最多在特定的 JVM 上。

提出替代方案:

如果你真的需要将数据存储在连续的内存中,为什么不在一个小的 C 库中进行并通过 JNI 调用呢?

于 2009-05-06T15:05:27.290 回答
2

我认为您会想要使用sun.java.unsafe

于 2009-05-06T14:59:27.570 回答
2

照我看来。你还没有解释为什么

  • 原始数组在内存中不是连续的。我不明白为什么它们在虚拟内存中不会连续。(cf 对象数组不太可能使其对象在内存中连续)
  • 在物理内存(RAM,即随机存取内存)中不连续的数组将具有显着的性能差异。例如,应用程序性能的可测量差异。

它看起来是你真的在寻找一种低级的方式来分配数组,因为你习惯于在 C 中这样做,而性能是需要这样做的要求。

顺便说一句:使用 getDouble()/putDouble() 访问 ByteBuffer.allocateDirect() 可能比仅使用 double[] 慢,因为前者涉及 JNI 调用,而后者可以优化为根本不调用。

使用它的原因是用于在 Java 和 C 空间之间交换数据。例如 NIO 调用。只有当读/写保持在最低限度时,它才会表现良好。否则你最好在 Java 空间中使用一些东西。

即,除非您清楚自己在做什么以及为什么要这样做,否则您最终会得到一个可能会让您感觉更好的解决方案,但实际上比简单的解决方案更复杂且性能更差。

于 2009-05-08T05:51:14.267 回答
0

请注意对相关问题的回答,该问题讨论了 System.identityHashCode() 和对象内存地址的标识。底线是您可以使用默认数组 hashCode() 实现来识别数组的原始内存地址(以适合 int/32 位为准)

于 2009-05-06T15:05:14.903 回答