Java中的连续页面/物理内存

时间:2009-05-06 14:48:55

标签: java memory-management operating-system x86 paging

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

我的问题是:

  • 为什么一个非常大的阵列确保 在物理内存中连续的页面?
  • 有没有办法确保在物理内存中分配数组,这不会让数组真的变大?
  • 如何判断Java对象/数组所在的页面或物理地址,而不测量缓存命中/缓存未命中?

我不是在寻找答案,问我为什么要在java中这样做。我知道C会“解决我的问题”,而且我反对java的基本性质。不过我有充分的理由这样做。

无法保证答案始终有效。我正在寻找大部分时间都有效的答案。创造性的,开箱即用的答案的额外点,没有合理的Java程序员会写。可以特定于平台(x86 32位64位)。

6 个答案:

答案 0 :(得分:6)

没有。物理上连续的内存需要与OS直接交互。大多数应用程序,包括JVM只能获得几乎连续的地址。并且JVM无法向您提供从OS获得的内容。

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

背景:

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

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

答案 1 :(得分:4)

鉴于垃圾收集器在(逻辑)内存中移动对象,我认为你将失去运气。

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

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

答案 2 :(得分:2)

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

答案 3 :(得分:2)

可能有办法欺骗特定的JVM做你想做的事情,但这些可能是脆弱的,复杂的,很可能非常特定于JVM,它的版本,它运行的操作系统等等。换句话说,浪费精力

因此,在不了解您的问题的情况下,我认为任何人都无法提供帮助。 一般来说,在Java中肯定没有办法,特别是在特定的JVM上。

建议替代方案:

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

答案 4 :(得分:2)

我看到了。你还没有解释原因

  • 原始数组在内存中不连续。我不明白为什么他们不会连续在虚拟内存中。 (参见对象阵列不太可能使其对象在内存中连续)
  • 在物理存储器中不连续的阵列(RAM即随机存取存储器)将具有显着的性能差异。例如您应用程序性能的可衡量差异。

它出现的是你真的在寻找一种分配数组的低级方法,因为你习惯于在C中这样做,而性能是需要这样做的主张。

BTW:使用getDouble()/ putDouble()访问ByteBuffer.allocateDirect()可能会慢一点,因为前者涉及JNI调用,后者可以优化为完全没有调用。

使用它的原因是用于在Java和C空间之间交换数据。例如NIO打电话。它只在读/写保持最小时才能很好地执行。否则你最好在Java空间中使用一些东西。

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

答案 5 :(得分:0)

注意this answer一个相关的问题,它讨论了System.identityHashCode()和对象内存地址的标识。底线是您可以使用默认数组hashCode()实现来标识数组的原始内存地址(取决于int / 32位)