angle-left

История утечки памяти в Java

В данной заметке расскажу о проблеме с которой я столкнулся при написании Java-приложения. В классе отвечающим за обмен данными с внешними носителями возвращаемое значение было объявлено и инициализировано локально (в методе) :

@Override
public synchronized byte[] getData(int size) {
byte[] b=new byte[size]; // утечка
try {
if (bInput.read(b, 0, b.length) == -1)
{
if ((fileIn = files.poll()) == null) {
b = null;
size--;
bInput.close();
fInput.close();
} else {
fInput = new FileInputStream(fileIn);
bInput = new BufferedInputStream(fInput);
return getData(size);
}
}
} catch (IOException ex) {
System.err.println("IOException in HDDStorage");
} finally {
this.size++;
return b;
}
}

Да, да, тут сразу многие поднимут тему perfomance при выделении памяти в куче при инициализации локальной переменной, но суть то в утечке памяти. Сам метод вызывается только из потоков в которых происходит манипуляция над данными ( считается хеш) и ссылка затирается.

if ((data = storageInData.getData(blockSize)) == null) { // чтение, data - переменная класса
return false;
…..
hash = md.digest(data); // хеширование, md — экземпляр java.security.MessageDigest
…..
return true;


 

Определил место утечки случайно, так как считал что гигантское количество памяти занимаемое byte[] используется для хранения хешей в HashMap (уж не удосужился посмотреть на count HashMap'а и length byte[]);

В процессе ̶б̶у̶р̶н̶о̶г̶о̶ ̶х̶о̶л̶и̶в̶а̶р̶а̶ ̶с̶ ̶д̶е̶в̶е̶л̶о̶п̶е̶р̶а̶м̶и̶ анализа происходящего, было выявлено, что причина утечки заключалась в удержании ссылок на использованные массивы в BufferedInputStream. (bug в openJDK1.6)
 

В итоге проблему решил следующим образом (малой кровью)

private byte[] b=new byte[512];
...
@Override
public synchronized byte[] getData(int size) {
if (b.length != size) {
b = new byte[size];
}
-//- …

Вывод : Избегайте использование локальных переменных больших размеров в часто используемых методах, как минимум по двум причинам :

  1. Perfomance

  2. Обход возможных утечек памяти
     

P.S. к вопросу производительности : общая производительность приложения после приведенных изменений возрасла в 1,6- 1,7 раз