我的目标是确保在连续的物理内存中分配java中分配的数组。我遇到的问题是,数组分配的页面在物理内存中往往不是连续的,除非我分配一个非常大的数组。
我的问题是:
我不是在寻找答案,问我为什么要在java中这样做。我知道C会“解决我的问题”,而且我反对java的基本性质。不过我有充分的理由这样做。
无法保证答案始终有效。我正在寻找大部分时间都有效的答案。创造性的,开箱即用的答案的额外点,没有合理的Java程序员会写。可以特定于平台(x86 32位64位)。
答案 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)
我看到了。你还没有解释原因
它出现的是你真的在寻找一种分配数组的低级方法,因为你习惯于在C中这样做,而性能是需要这样做的主张。
BTW:使用getDouble()/ putDouble()访问ByteBuffer.allocateDirect()可能会慢一点,因为前者涉及JNI调用,后者可以优化为完全没有调用。使用它的原因是用于在Java和C空间之间交换数据。例如NIO打电话。它只在读/写保持最小时才能很好地执行。否则你最好在Java空间中使用一些东西。
即。除非你清楚自己在做什么以及为什么你正在做什么,否则你最终可能会得到一个可能让你感觉更好的解决方案,但实际上它比简单的解决方案更复杂,性能更差。 / p>
答案 5 :(得分:0)
注意this answer一个相关的问题,它讨论了System.identityHashCode()和对象内存地址的标识。底线是您可以使用默认数组hashCode()实现来标识数组的原始内存地址(取决于int / 32位)