Por que recebo “Não há armazenamento suficiente disponível para processar este comando” usando Java MappedByteBuffers?

StackOverflow https://stackoverflow.com/questions/1927123

  •  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.

Foi útil?

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.

  1. Java tem uma restrição fundamental de que o length atributo de uma matriz e os índices de matriz têm tipo int.Isto, combinado com o fato de que int está assinado e um array não pode ter um tamanho negativo significa que o maior array possível pode ter 2**31 elementos.Esta restrição se aplica a JVMs de 32 e 64 bits.É uma parte fundamental da linguagem Java ...como o fato de que char os valores vão de 0 para 65535.

  2. 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 que 2**32 bytes se for espaço de endereço.)

  3. 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.

  4. 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.)

  5. 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 e 2**32 bytes de memória endereçável.Isso é espaço suficiente para um MÁXIMO entre 2**29 e 2**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 seria 2**63 bytes ou 2**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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top