Por que recebo “Não há armazenamento suficiente disponível para processar este comando” usando Java MappedByteBuffers?
-
20-09-2019 - |
Pergunta
Eu tenho uma matriz muito grande de duplos que estou usando um arquivo baseado em disco e uma lista de paginação de MappedByteBuffers para manipular, consulte essa questão para obter mais informações.Estou executando o Windows XP usando Java 1.5.
Aqui está a parte principal do meu código que faz a alocação dos buffers no arquivo ...
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;
}
Recebo o erro mencionado no título depois de alocar o segundo ou terceiro buffer.
Achei que tinha algo a ver com a memória contígua disponível, então tentei com diferentes tamanhos e números de páginas, mas sem nenhum benefício geral.
O que exatamente faz "Não está disponível armazenamento suficiente para processar este comando" significa e o que, se houver alguma coisa, posso fazer sobre isso?
Achei que o objetivo do MappedByteBuffers era a capacidade de lidar com estruturas maiores do que cabem no heap e tratá-las como se estivessem na memória.
Alguma pista?
EDITAR:
Em resposta a uma resposta abaixo (@adsk), mudei meu código para nunca ter mais do que um único MappedByteBuffer ativo ao mesmo tempo.Quando me refiro a uma região do arquivo que não está mapeada no momento, descarto o mapa existente e crio um novo.Ainda recebo o mesmo erro após cerca de 3 operações no mapa.
O bug citado com o GC não coletando os MappedByteBuffers ainda parece ser um problema no JDK 1.5.
Solução
Achei que o objetivo do MappedByteBuffers era a capacidade de lidar com estruturas maiores do que cabem no heap e tratá-las como se estivessem na memória.
Não.A ideia é / era permitir abordar mais de 2 ** 31 duplas ...supondo que você tenha memória suficiente e esteja usando uma JVM de 64 bits.
(Estou assumindo que esta é uma pergunta de acompanhamento para essa questão.)
EDITAR:Claramente, mais explicações são necessárias.
Existem vários limites que entram em jogo.
Java tem uma restrição fundamental de que o
length
atributo de uma matriz e os índices de matriz têm tipoint
.Isto, combinado com o fato de queint
está assinado e um array não pode ter um tamanho negativo significa que o maior array possível pode ter2**31
elementos.Esta restrição se aplica a JVMs de 32 e 64 bits.É uma parte fundamental da linguagem Java ...como o fato de quechar
os valores vão de0
para65535
.Usar uma JVM de 32 bits coloca um limite superior (teórico) de
2**32
no número de bytes endereçáveis pela JVM.Isso inclui todo o heap, seu código e classes de biblioteca que você usa, o núcleo do código nativo da JVM, a memória usada para buffers mapeados...tudo.(Na verdade, dependendo da sua plataforma, o sistema operacional poderia dar-lhe consideravelmente menos do que2**32
bytes se for espaço de endereço.)Os parâmetros fornecidos na linha de comando java determinam quanta memória heap a JVM irá permitir seu aplicativo a ser usado.Memória mapeada para usar
MappedByteBuffer
objetos não contam para isso.A quantidade de memória que o sistema operacional fornecerá depende (no Linux/UNIX) da quantidade total de espaço de troca configurado, dos limites do 'processo' e assim por diante.Limites semelhantes provavelmente se aplicam ao Windows.E, claro, você só pode executar uma JVM de 64 bits se o sistema operacional host for compatível com 64 bits e você estiver usando hardware compatível com 64 bits.(Se você tem um Pentium, está sem sorte.)
Finalmente, a quantidade de memória física do seu sistema entra em jogo.Em teoria, você pode pedir à sua JVM para usar um heap, etc., que é muitas vezes maior que a memória física da sua máquina.Na prática, este é um péssima ideia.Se você alocar memória virtual demais, seu sistema sofrerá problemas e o desempenho do aplicativo diminuirá.
A conclusão é esta:
Se você usa uma JVM de 32 bits, provavelmente estará limitado a algo entre
2**31
e2**32
bytes de memória endereçável.Isso é espaço suficiente para um MÁXIMO entre2**29
e2**30
duplica, quer você use um array ou um buffer mapeado.Se você usar uma JVM de 64 bits, poderá representar uma única matriz de
2**31
dobra.O limite teórico de um Buffer mapeado seria2**63
bytes ou2**61
dobra, mas o limite prático seria aproximadamente a quantidade de memória física que sua máquina possui.
Outras dicas
Ao mapear a memória um arquivo, é possível ficar sem espaço de endereço na VM de 32 bits. Isso acontece mesmo que o arquivo seja mapeado em pequenos pedaços e esses bytesbuffers não estejam mais acessíveis. O motivo é que o GC nunca começa a libertar os buffers.
Consulte o bug em http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6417205