2016-01-03 3 views
3

Я использую Jetty 9.3.5.v20151012 для доставки большого количества событий клиентам с использованием веб-карт. События состоят из трех частей: числа, типа события и временной метки, а каждое событие сериализуется как байт [] и отправляется с использованием ByteBuffer.Jetty - Возможная утечка памяти при использовании веб-узлов и ByteBuffer

После определенного количества часов/дней, в зависимости от количества клиентов, я замечаю увеличение объема памяти кучи и не дает возможности GC восстановить его. Когда куча (установленная на 512 МБ) почти заполнена, память, используемая jvm, составляет около 700-800 МБ, а ЦП составляет 100% (это похоже на то, что GC очень часто пытается очистить). В начале, когда я запускаю Jetty, при вызове GC память составляет около 30 МБ, но через некоторое время это число увеличивается все больше и больше. В конце концов процесс убит.

Я использую jvisualvm, как профайлер для отладки утечек памяти, и я приложил некоторые скриншоты из головы отвала:

enter image description here enter image description here enter image description here

Вот основной код, который обрабатывает отправки сообщений используя ByteBuffer:

У меня в основном есть метод, который создает байт [] (fullbytes) для всех событий, которые необходимо отправить в одном сообщении:

byte[] numberBytes = ByteBuffer.allocate(4).putFloat(number).array(); 
byte[] eventBytes = ByteBuffer.allocate(2).putShort(event).array(); 
byte[] timestampBytes = ByteBuffer.allocate(8).putDouble(timestamp).array(); 

for (int i = 0; i < eventBytes.length; i++) { 
    fullbytes[i + scount*eventLength] = eventBytes[i]; 
} 

for (int i = 0; i < numberBytes.length; i++) { 
    fullbytes[eventBytes.length + i + scount*eventLength] = numberBytes[i]; 
} 

for (int i = 0; i < timestampBytes.length; i++) { 
    fullbytes[numberBytes.length + eventBytes.length + i + scount*eventLength] = timestampBytes[i]; 
} 

А потом еще один метод (называемый в отдельном потоке), который посылает байты на WebSockets

ByteBuffer bb = ByteBuffer.wrap(fullbytes); 
wsSession.getRemote().sendBytesByFuture(bb); 
bb.clear(); 

Как я уже читал на нескольких месте (в документации или here и here), это вопрос не должен появляться, так как я не использую прямой ByteBuffer. Может быть, это ошибка, связанная с Jetty/websockets?

Обратите внимание!

EDIT:

Я сделал еще несколько тестов, и я заметил, что проблема возникает при отправке сообщений клиента, который не подключен, но молы не получили OnClose события (напр. пользователь переносит свой ноутбук в режим ожидания). Поскольку событие on close не запускается, код сервера не отменяет регистрацию клиента и продолжает пытаться отправить сообщения этому клиенту. Я не знаю, почему, но закрытие происходит через 1 или 2 часа. Кроме того, иногда (пока еще не знакомы с контекстом), хотя событие получено и клиент (сокет) не зарегистрирован, ссылка на объект WebSocketSession (для этого клиента) все еще зависает. Я не узнал, почему это происходит.

До тех пор, у меня есть 2 возможных путей их устранения, но я понятия не имею, как достичь их (то есть и другие хорошие использования, а):

  1. Всегда обнаружить, когда соединение не открыто (или временно закрыт , например, пользователь переносит ноутбук в режим ожидания). Я попытался использовать sendPing() и реализовать onFrame(), но я не смог найти решение. Есть ли способ сделать это?
  2. Периодически «промывать» буфер. Как я могу отказаться от сообщений, которые не были отправлены клиенту, чтобы они не останавливались в очереди?

EDIT 2

Это может указывать на тему в другую сторону, поэтому я сделал еще один пост here.

EDIT 3

Я сделал еще несколько тестов относительно большого числа сообщений/байт, отправленные и я узнал, почему «это швы», что утечка памяти только иногда возникала: при отправке байт на асинхронном другой поток, чем тот, который используется при вызове sevlet.configure(), после большого наращивания память не освобождается после отсоединения клиента. Также я не смог имитировать утечку памяти при использовании sendBytes (ByteBuffer), только с помощью sendBytesByFuture (ByteBuffer) и sendBytes (ByteBuffer, WriteCallback).

Это швы очень странные, но я не верю, что делаю что-то «неправильное» в тестах.

Код:

@Override 
public void configure(WebSocketServletFactory factory) { 
    factory.getPolicy().setIdleTimeout(1000 * 0); 
    factory.setCreator(new WebSocketCreator() { 

    @Override 
    public Object createWebSocket(ServletUpgradeRequest req, 
      ServletUpgradeResponse resp) { 
     return new WSTestMemHandler(); 
    } 
}); 
} 

@WebSocket 
public class WSTestMemHandler { 
    private boolean connected = false; 
    private int n = 0; 

    public WSTestMemHandler(){ 
    } 

    @OnWebSocketClose 
    public void onClose(int statusCode, String reason) { 
     connected = false; 
     connections --; 
     //print debug 
    } 

    @OnWebSocketError 
    public void onError(Throwable t) { 
    //print debug 
    } 

    @OnWebSocketConnect 
    public void onConnect(final Session session) throws InterruptedException { 

     connected = true; 
     connections ++; 
    //print debug 

     //the code running in another thread will trigger memory leak 
     //when to client endpoint is down and messages are still sent 
     //because the GC will not cleanup after onclose received and 
     //client disconnects 

     //if the "while" loop is run in the same thread, the memory 
     //can be released when onclose is received, but that would 
     //mean to hold the onConnect() method and not return. I think 
     //this would be bad practice. 

     new Thread(new Runnable() { 

      @Override 
      public void run() { 

       while (connected) { 

        testBytesSend(session); 
        try { 
         Thread.sleep(4); 
        } catch (InterruptedException e) { 
        } 

       } 
       //print debug 
      } 
     }).start(); 


    } 



    private void testBytesSend(Session session) { 

     try { 
      int noEntries = 200; 
      ByteBuffer bb = ByteBuffer.allocate(noEntries * 14); 
      for (int i = 0; i < noEntries; i++) { 
       n+= 1.0f; 
       bb.putFloat(n); 
       bb.putShort((short)1); 
       bb.putDouble(123456789123.0); 
      } 
      bb.flip(); 


      session.getRemote().sendBytes(bb, new WriteCallback() { 

       @Override 
       public void writeSuccess() { 

       } 

       @Override 
       public void writeFailed(Throwable arg0) { 

       } 
      }); 


     //print debug 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 


} 
+0

Как вы определяете, что «без возможности для GC восстановить его»? Вы получаете OutOfMemoryError? – Kayaman

+0

Привет, я редактировал свой пост. –

+0

Когда куча (установленная на 512 МБ) почти заполнена, память, используемая jvm, составляет около 700-800 МБ, а ЦП - 100% (это похоже на то, что GC очень часто пытается очистить). В начале, когда я запускаю Jetty, при вызове GC память составляет около 30 МБ, но через некоторое время это число увеличивается все больше и больше. В конце концов процесс убит. Я не дождался, пока он полностью выйдет из памяти, потому что процессор находится на 100%, и сервер становится непригодным; И я не могу шов, чтобы воспроизвести проблему в процессе развития. –

ответ

1

Ваше ByteBuffer использование невероятно неэффективно.

Не создавайте все эти мелкие/крошечные ByteBuffers, чтобы получить массив байтов, а затем выбросить его. Ик.

Примечания: вы даже не использовать .array() вызов правильно, так как не все ByteBuffer распределения имеет множество подкладочного вы можете получить доступ, как это.

байт массива numberBytes, eventBytes, timestampBytes и fullbytes не должны существовать.

Создайте единый ByteBuffer, представляющий все сообщение, которое вы собираетесь отправить, выделите его как размером, который вам нужен, так и большим.

Затем поместите в него отдельные байты, переверните его и дайте реалистичной реализации Jetty ByteBuffer.

Причал будет использовать стандартную ByteBuffer информацию (например, positionlimit и), чтобы определить, какая часть этого ByteBuffer на самом деле должны быть отправлены.

+0

Итак, если я правильно понимаю, чтобы предотвратить утечку памяти: я должен использовать методы putFloat, putShort, putDouble (вместо создания массивов для каждой информации о событиях и с использованием метода обхода) и называть их одним и тем же объектом ByteBuffer; затем используйте flip(), чтобы пометить его для чтения, а затем посылая его на веб-узлы? А также, должен ли я использовать тот же ByteBuffer для всех отправленных сообщений и не создавать новые объекты ByteBuffer для каждого из них? (или для этого последнего нет разницы?) –

+0

Что касается последнего вопроса в вашем комментарии, как только вы передадите ByteBuffer на Jetty, он больше не будет вашим, не используйте его повторно. –

+0

Привет, еще раз, я не нашел причину утечки памяти, но это заставляет, что какой-то код держит причал от освобождения связанных с сеансом объектов. Это может быть что-то от того, как я использую factory.setCreator() в webSocketServlet.configure(), но я все еще работаю над этим ... Тем временем я пытаюсь найти другие решения/обходные пути. –