为什么我使用 Java MappedByteBuffers 时收到“没有足够的存储空间来处理此命令”?
-
20-09-2019 - |
题
我有一个非常大的双精度数组,我正在使用基于磁盘的文件和 MappedByteBuffers 的分页列表来处理,请参阅 这个问题 了解更多背景。我正在使用 Java 1.5 在 Windows XP 上运行。
这是我的代码的关键部分,它针对文件分配缓冲区......
try
{
// create a random access file and size it so it can hold all our data = the extent x the size of a double
f = new File(_base_filename);
_filename = f.getAbsolutePath();
_ioFile = new RandomAccessFile(f, "rw");
_ioFile.setLength(_extent * BLOCK_SIZE);
_ioChannel = _ioFile.getChannel();
// make enough MappedByteBuffers to handle the whole lot
_pagesize = bytes_extent;
long pages = 1;
long diff = 0;
while (_pagesize > MAX_PAGE_SIZE)
{
_pagesize /= PAGE_DIVISION;
pages *= PAGE_DIVISION;
// make sure we are at double boundaries. We cannot have a double spanning pages
diff = _pagesize % BLOCK_SIZE;
if (diff != 0) _pagesize -= diff;
}
// what is the difference between the total bytes associated with all the pages and the
// total overall bytes? There is a good chance we'll have a few left over because of the
// rounding down that happens when the page size is halved
diff = bytes_extent - (_pagesize * pages);
if (diff > 0)
{
// check whether adding on the remainder to the last page will tip it over the max size
// if not then we just need to allocate the remainder to the final page
if (_pagesize + diff > MAX_PAGE_SIZE)
{
// need one more page
pages++;
}
}
// make the byte buffers and put them on the list
int size = (int) _pagesize ; // safe cast because of the loop which drops maxsize below Integer.MAX_INT
int offset = 0;
for (int page = 0; page < pages; page++)
{
offset = (int) (page * _pagesize );
// the last page should be just big enough to accommodate any left over odd bytes
if ((bytes_extent - offset) < _pagesize )
{
size = (int) (bytes_extent - offset);
}
// map the buffer to the right place
MappedByteBuffer buf = _ioChannel.map(FileChannel.MapMode.READ_WRITE, offset, size);
// stick the buffer on the list
_bufs.add(buf);
}
Controller.g_Logger.info("Created memory map file :" + _filename);
Controller.g_Logger.info("Using " + _bufs.size() + " MappedByteBuffers");
_ioChannel.close();
_ioFile.close();
}
catch (Exception e)
{
Controller.g_Logger.error("Error opening memory map file: " + _base_filename);
Controller.g_Logger.error("Error creating memory map file: " + e.getMessage());
e.printStackTrace();
Clear();
if (_ioChannel != null) _ioChannel.close();
if (_ioFile != null) _ioFile.close();
if (f != null) f.delete();
throw e;
}
分配第二个或第三个缓冲区后,出现标题中提到的错误。
我认为这与可用的连续内存有关,因此尝试使用不同大小和数量的页面,但总体上没有任何好处。
到底是做什么的 “没有足够的存储空间来处理此命令” 意思是,如果有的话,我能做些什么呢?
我认为 MappedByteBuffers 的要点是能够处理大于堆所能容纳的结构,并将它们视为内存中的结构。
有什么线索吗?
编辑:
为了回应下面的答案(@adsk),我更改了代码,因此我在任何时候都不会拥有超过一个活动的 MappedByteBuffer。当我引用当前未映射的文件区域时,我会废弃现有映射并创建一个新映射。大约 3 次地图操作后我仍然遇到同样的错误。
GC 未收集 MappedByteBuffers 引用的错误似乎仍然是 JDK 1.5 中的问题。
解决方案
我认为 MappedByteBuffers 的要点是能够处理大于堆所能容纳的结构,并将它们视为内存中的结构。
不。这个想法是/是让你解决超过 2**31 双打......假设您有足够的内存,并且使用的是 64 位 JVM。
(我假设这是一个后续问题 这个问题.)
编辑:显然,需要更多解释。
有许多限制在起作用。
Java 有一个基本限制,即
length
数组的属性,数组索引具有类型int
. 。这一点,结合以下事实int
有符号并且数组不能有负数意味着最大可能的数组可以有2**31
元素。此限制适用于 32 位和 64 位 JVM。它是 Java 语言的基本组成部分...就像这样一个事实char
值来自0
到65535
.使用 32 位 JVM 的(理论上)上限为
2**32
JVM 可寻址的字节数。这包括整个堆、您的代码和您使用的库类、JVM 的本机代码核心、用于映射缓冲区的内存......一切。(事实上,取决于您的平台、操作系统 可能 给你大大少于2**32
如果地址空间为字节。)您在 java 命令行上给出的参数决定了 JVM 将使用多少堆内存 允许 您要使用的应用程序。映射到使用的内存
MappedByteBuffer
对象不计入此。操作系统提供的内存量取决于(在 Linux/UNIX 上)配置的交换空间总量、“进程”限制等。类似的限制可能也适用于 Windows。当然,如果主机操作系统支持 64 位,并且您使用的是支持 64 位的硬件,则只能运行 64 位 JVM。(如果你有奔腾,那你就运气不好了。)
最后,系统中的物理内存量会发挥作用。理论上,您可以要求 JVM 使用比您的机器物理内存大很多倍的堆等。在实践中,这是一个 馊主意. 。如果您过度分配虚拟内存,您的系统将会崩溃,应用程序性能也会下降。
要点是:
如果您使用 32 位 JVM,您可能会受到限制
2**31
和2**32
可寻址内存字节。这足以容纳 MAXIMUM 之间的空间2**29
和2**30
双精度数,无论您使用数组还是映射缓冲区。如果您使用 64 位 JVM,则可以表示单个数组
2**31
双打。映射缓冲区的理论限制是2**63
字节或2**61
双倍,但实际限制大约是您机器拥有的物理内存量。
其他提示
当存储器映射文件,有可能在32位VM运行的地址空间。出现这种情况即使该文件是一小块一小块映射和那些字节缓冲区不再可达。其原因是,GC从未踢以释放缓冲器。
参考错误在 http://bugs.sun.com/bugdatabase /view_bug.do?bug_id=6417205