Почему я получаю «Недостаточно памяти для обработки этой команды», используя Java MappedByteBuffers? - PullRequest
2 голосов
/ 18 декабря 2009

У меня есть очень большой массив значений типа double, в котором я использую файл на диске и список страниц MappedByteBuffers для обработки, см. этот вопрос для получения дополнительной информации. Я работаю на Windows XP с использованием Java 1.5.

Вот ключевая часть моего кода, которая выполняет распределение буферов по файлу ...

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 заключается в способности обрабатывать структуры, которые больше, чем вы можете уместить в куче, и обращаться с ними, как будто они находятся в памяти.

Есть какие-нибудь подсказки?

EDIT:

В ответ на ответ ниже (@adsk) я изменил свой код, чтобы у меня никогда не было более одного активного MappedByteBuffer за один раз. Когда я ссылаюсь на область файла, которая в данный момент не отображена, я создаю ненужную карту и создаю новую. Я все еще получаю ту же ошибку после примерно 3 операций с картой.

Ошибка, процитированная с GC, не собирающей MappedByteBuffers, все еще кажется проблемой в JDK 1.5.

Ответы [ 2 ]

3 голосов
/ 18 декабря 2009

Я подумал, что смысл MappedByteBuffers заключается в способности обрабатывать структуры, которые больше, чем вы можете поместить в кучу, и обращаться с ними так, как если бы они были в памяти.

Нет. Идея была / состояла в том, чтобы позволить вам обратиться к более чем 2 ** 31 двойному символу ... при условии, что у вас достаточно памяти и вы используете 64-битную JVM.

(Я предполагаю, что это дополнительный вопрос к этому вопросу .)

РЕДАКТИРОВАТЬ : Понятно, что требуется больше объяснений.

Есть ряд ограничений, которые вступают в игру.

  1. У Java есть фундаментальное ограничение: атрибут length массива и индексы массива имеют тип int. Это, в сочетании с тем фактом, что int подписано и массив не может иметь отрицательный размер, означает, что в максимально возможном массиве может быть 2**31 элементов. Это ограничение применяется к 32-битным и 64-битным JVM. Это фундаментальная часть языка Java ... как тот факт, что значения char изменяются от 0 до 65535.

  2. Использование 32-битной JVM устанавливает (теоретическую) верхнюю границу 2**32 на количество байтов, которые могут быть адресованы JVM. Это включает в себя всю кучу, ваш код и используемые вами библиотечные классы, ядро ​​собственного кода JVM, память, используемую для отображаемых буферов ... все. (Фактически, в зависимости от вашей платформы ОС может дать вам значительно меньше 2**32 байтов, если адресное пространство.)

  3. Параметры, которые вы задаете в командной строке java, определяют, сколько динамической памяти JVM разрешит вашему приложению для использования. Память, сопоставленная с использованием MappedByteBuffer объектов, не учитывается.

  4. Объем памяти, который ОС предоставит вам, зависит (для Linux / UNIX) от общего количества настроенного пространства подкачки, пределов «процесса» и так далее. Подобные ограничения, вероятно, применяются к Windows. И, конечно, вы можете запустить 64-битную JVM только в том случае, если хост-операционная система поддерживает 64-битную архитектуру и вы используете 64-битную аппаратную часть. (Если у вас есть Pentium, вам просто не повезло.)

  5. Наконец, количество физической памяти в вашей системе вступает в игру. Теоретически, вы можете попросить JVM использовать кучу и т. Д., Которые во много раз больше физической памяти вашей машины. На практике это плохая идея . Если вы перераспределите виртуальную память, ваша система будет зависать, а производительность приложений будет падать.

Забрать это:

  • Если вы используете 32-битную JVM, вы, вероятно, ограничены где-то между 2**31 и 2**32 байтами адресуемой памяти. Этого достаточно для МАКСИМУМА между удвоениями 2**29 и 2**30, независимо от того, используете ли вы массив или отображенный буфер.

  • Если вы используете 64-битную JVM, вы можете представить один массив из 2**31 double. Теоретический предел отображаемого буфера будет 2**63 байтов или 2**61, удваивается, но практический предел будет примерно равен объему физической памяти вашей машины.

1 голос
/ 18 декабря 2009

При отображении файла в память возможно исчерпание адресного пространства в 32-битной виртуальной машине. Это происходит, даже если файл отображается небольшими порциями, и эти байтовые буферы больше недоступны. Причина в том, что GC никогда не включается, чтобы освободить буферы.

См. Ошибку на http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6417205

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...