?

Log in

No account? Create an account
entries friends calendar profile My Website Previous Previous Next Next
Сведение к известному абсурду - Уголок Школьника
scolar
scolar
Сведение к известному абсурду

Возились на днях с ошибкой: некоторый java-сервлет отказывается обрабатывать данные, больше некоторого размера. Увеличение памяти, доступной Tomcat, ничего не меняет.

Выясняется: у нас в коде есть вызов OutputStream.write(byte[]) - то есть, натурально, запись массива байт целиком.

В OutputStream есть и другой вариант, на случай, если нужно записать не весь массив: OutputStream.write(byte[], int offset, int length) - поэтому, казалось бы, нет ничего удивительного в том, как эти две функции реализованны в классе FileOutputStream:

public void write(byte b[]) throws IOException {
  writeBytes(b, 0, b.length);
}

public void write(byte b[], int off, int len) throws IOException {
  writeBytes(b, off, len);
}

Нет, в сущности, ничего удивительного и в том, что функция writeBytes объявлена как native и, соответственно, реализована на C.

Те, кто придумывал интерфейс JNI, были люди с воображением, и потому понимали, что задача получения C-указателя на Java-массив, вообще говоря, может решаться по-разному в зависимости от обстоятельств и потребностей. Поэтому они ввели две функции:
- GetByteArrayElements, которая выдаёт указатель либо на весь массив, либо на его копию (в том смысле, что ничего не гарантировано);
- GetByteArrayRegion, которая выдаёт указатель заведомо на копию подмассива.

Разумеется, при реализации writeBytes воспользовались второй. Сделали это следующим образом: объявляется локальный массив некоторого размера (соттветственно, на стеке), но если запрашиваемый кусок больше, то с помощью malloc выделяется память на куче. Причём, как мы понимаем, это не та куча, размер которой мы указываем, конфигурируя Tomcat. Дальше происходит копирование Java-массива в C-массив, ну и вызов функции write из биоблиотеки C с созданным С-массивом в качестве аргумента.

Итого: вызов безобидной функции write приводит к выделению дополнительной памяти в размере входных данных, копированию и освобождению памяти.

Мне пока не удалось понять природу мировой константы в районе 70 мегабайт, на которой malloc перестаёт давать память и в Java выкидывается OutOfMemoryException - но это и не важно. Будем ампутировать хвост по частям.

Нашёлся, впрочем, славный workaround: дело в том, что для FtpOutputStream, который метод write наследует от FilteredOutputStream, реализация такова:

public void write(byte b[]) throws IOException {
  write(b, 0, b.length);
}

public void write(byte b[], int off, int len) throws IOException {
  if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
    throw new IndexOutOfBoundsException();

  for (int i = 0 ; i < len ; i++) {
    write(b[off + i]);
  }
}

Соответственно, установка FTP сервера и указание в качестве пункта назначения ftp вместо локального файла, позволяет решить возникшие в процессе эксплуатации проблемы.
2 comments or Leave a comment
Comments
liebelyuba From: liebelyuba Date: September 23rd, 2008 01:19 am (UTC) (Link)

OFF

можно я тебя зафренжу, раз уж мы в Mountain View перебрались? Буду тебя как местную газету читать. Ну, или в гости можно друг к другу ходить. (Я знаю, что у тебя детки, но не знаю, сколько им лет (?))
scolar From: scolar Date: September 23rd, 2008 03:08 am (UTC) (Link)

Re: OFF

В гости приходите. Деткам нашим 4- и 1+. Месиная газета выходит нерегулярно, но читать не возбраняется.
2 comments or Leave a comment