16 июля в 15:11

Java и без 16Gb памяти?

Java*
Однажды меня посетила мысль о том, что надо закодить что-нибудь на Java для RaspberryPI. Предыстория того, как я дошёл до жизни такой, сама по себе потянет на отдельный пост. Но вот сочные технические подробности, трудности и счастливый конец ниже под катом.





Постановка задачи



Немного разочаровавшись в движении проекта satnogs, я решил попробовать сам написать базовую станцию для приёма радио сигналов на raspberry pi. Проанализировав текущую функциональность satnogs и сложив с собственным заскорузлым enterprise пониманием того, что такое стабильная платформа, я придумал следующие требования:

  • java вместо python. Конечно же.
  • низкое потребление ресурсов. Embedded же.
  • переиспользование уже существующих библиотек. Цель проекта не научиться декодировать самому, а максимально интегрировать уже существующие библиотеки
  • стабильность. Коробочка должна работать сама по себе как можно дольше. В идеале её нужно настроить и забыть.


В результате в противоречие вступают только два требования: Java и низкое потребление ресурсов.

В этот момент я почему то вспомнил древний древний слоган «Java — write once, run everywhere» и присказку, что Java может запускаться на кофеварке. С этого момента началось погружение в Java Embedded.

Если вкратце, то в Java существуют две платформы для написания под маленькие устройства: Java ME и Java Embedded. Первая платформа предназначена для совсем маленьких (кофеварки) устройств, а вторая для тех, что чуть-чуть покрупнее. Я выбрал Java Embedded.

Сама Java Embedded в Java 8 претерпела изменения. Теперь её можно собрать с различными профайлами: compact1, compact2, compact3. По сути, это depedency management для бедных. Каждый профайл содержит какие-то части rt.jar, тем самым уменьшая минимальное потребление памяти JVM при загрузке. На моих как-бы тестах (колонка %RES в выводе команды top), я получил следующее потребление:

  • compact1 — 10mb
  • compact2 — 12mb


Для начала я выбрал самый хардкорный вариант: compact1. Нo если не получится найти под него библиотеки, то можно попробовать compact2.

После выбора версии Java нужно выбрать библиотеки. И вот тут дикий-дикий запад. Поскольку в Java мире всё течёт неспеша и с оглядкой на обратную совместимость, то никто из разработчиков библиотек не побежал оптимизировать свой код под новые профайлы. Тем более скоро выходит Java 9, где всё может ещё раз измениться.

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

IoC фреймворк


  • Dagger, Feather — нет @PreDestroy, @PostConstruct и принципиально не планируется. Про graceful shutdown разработчики видимо не слышали. Вручную контролировать последовательность вызова метода start, чтобы при остановке в обратном порядке вызвать stop, совсем не хочется делать.
  • Guice — зависимость на guava, а значит ещё +2mb.
  • picocontainer — не compact1


База данных



Какой же Java проект без базы данных. Но тут есть один подвох: в compact1 нет java.sql api. Поэтому я первым делом посмотрел на базы с native api без jdbc:

  • berkleydb. NoSQL, но почему-то зависит от javax.transactional.


И с jdbc:

  • sqlite — библиотека весит 5mb. Видимо содержит все нативные библиотеки для всех платформ.
  • java db. Весит конечно много и разные версии отличаются существенно: 10.8 — 2.5mb, 10.13 — 3.1mb.


Есть ещё куча других мелких непонятных embedded баз данных, которые можно было бы попробовать. Но отлавливать их баги под raspberry pi у меня желания нет.

Зато есть пара других идей:

  1. А что, если обхитрить JVM: взять compact1 и вручную подложить java.sql api? Ответ: не получится. В Classloader есть вот такой замечательный код:

           if ((name != null) && name.startsWith("java.")) {
                throw new SecurityException
                    ("Prohibited package name: " +
                     name.substring(0, name.lastIndexOf('.')));
            }
    


    Вообще непонятно почему существует такой maven артефакт, если его даже теоретически нельзя загрузить.
  2. А может без базы? Для моих целей вполне подходят обычные файлы. Sql join тоже вроде не имеет смысла делать.


В общем отказался совсем от базы. Посмотрим надолго ли.

Web container


  • tomcat — Ха-ха-ха
  • jetty — не compact1
  • nanohttpd — не servlet, нет поддержки сессий. Но видимо такова судьба Embedded разработчика.


SSL temination


  • nginx. 3mb master node + 3mb 1 client worker. = 6mb. Вроде неплохо.


Вэб клиент


  • angular, reactjs — на ровном месте привносят десяток короткоживущих технологий.
  • good-o-templates — наш выбор же.


Шаблонизаторы


  • JSP — слишком тяжело и нужно много библиотек. Даже не стал копать.
  • Freemarker — легко, но как оказалось не compact1.
  • Кто-нибудь слышал про jtwig? Я тоже нет, но они умееют работать в compact1 и поддерживают базовые фичи.


Логирование


  • logback — только compact3
  • log4j — full JRE
  • java.util.logging? — Хуже уже не будет.


Json


  • gson. Зависимость на java.sql (!!!)
  • jacksonxml. Зависимость на org.w3c.dom.Node
  • очередной «нагуглил-ночью» код https://github.com/ralfstx/minimal-json. Посмотрел, вроде там нечему ломаться.


После нескольких запусков и сборке всего вместе выплыло несколько косяков, но их можно поправить конфигурацией. Например:
https://stackoverflow.com/questions/13825403/java-how-to-get-logger-to-work-in-shutdown-hook

Итого


  • все библиотеки в сборе + прогретый кэш для шаблонизатора занимают в памяти ~23mb
  • код открыт и доступен: https://github.com/dernasherbrezon/r2cloud (надеюсь пароли нигде там не закоммитил)
@dernasherbrezon
карма
11,0
рейтинг 27,2
Пользователь
Самое читаемое Разработка

Комментарии (27)

  • +1
    Было бы интересно посмотреть для сравнения, сколько было бы потребление памяти при использовании полноценной Java.
    Мне кажется, RPi должен спокойно тянуть по объему памяти Java SE с БД и прочими удовольствиями.
  • +3
    А какой смысл в экономии памяти, коей на актуальной распберри гигабайт? Гоняем на ней клиентскую часть под оракловой java 8, данные лежат в MySQL (~150k записей в самой жирной таблице), поверх него натянут EclipseLink (лол, но это работает). В пике потребления (распаковка архива при импорте) получается ~400 Мб, в работе — 150-200Мб, остальное отдано под кэш превьюшек. Не скажу, что всё это очень шустро бегает, но тормоза отрисовки UI неизмеримо больше, чем работы с данными.
    • +2
      Для данного проекта web ui должен занимать как можно меньше ресурсов, потому что это не основная фича приложения.
  • 0
    Так что с Ioc и базой? Никак?
    А то я сегодня удивился, что Spring Boot Web на хелловорлде только 73 МБ оперативы занял.
    • 0
      А что такое loc?
      • 0
        IoC фреймворк
        • 0
          Да, проще стримить данные куда то вовне и уже там делать join и aggregation если нужно.

          А маленького IoC который бы мог хотя бы делать PostConstruct/PreDestroy я не нашел.
  • 0
    del
  • +1
    Всегда хотел, чтобы для Java и JVM-языков были средства для создания легковесных приложений. Дело даже не в ограничениях железа, а в том, что с такими программами проще. Легковесное приложение можно поставить на любую машину (не важно, сколько сервисов на ней уже крутится), можно порекомендовать любому человеку. Если это основное приложение на компьютере, то да, плевать, сколько оно там памяти потребляет. А от сервиса, который используется раз в день, хочется экономного расходования ресурсов.

    Пример — Subsonic (self-hosted аналог сервисов типа Google Music и ему подобных). Обычно я пользуюсь облачным службами для стриминга музыки, но редкие вещи продолжаю хранить у себя и ради этого настроил Subsonic. Проблема в том, что он съедает 200-300 мегабайт памяти и время от времени грузит процессор. А ещё требует проприетарную JRE (я не доверяю Oracle). Всё это ради того, чтобы раз в день послушать песенку? В итоге я удалил его и поставил более легковесный аналог.

    В этом плане очень хорош Go. Достаточно высокоуровневый язык, статические бинарики на выходе, довольно экономный с точки зрения памяти. И если вдруг понадобится разработать какой-нибудь сервис, я выберу именно его. Но писать приятнее всё-таки на Java/Kotlin.

    Пробовал Avian в качестве альтернативной JRE, но проекту ещё расти и расти для более-менее серьёзного применения. Java Embedded — сложности с библиотеками, не буду повторять автора. Было бы очень хорошо, если бы сформировалось коммьюнити Java Embedded, которое бы готовило урезанные версии библиотек (либо писало их аналоги, если урезать не получается). Но слишком мала вероятность, что такое произойдет.
    • 0
      Всё это ради того, чтобы раз в день послушать песенку? В итоге я удалил его и поставил более легковесный аналог
      а какой именно?
    • 0
      Всегда хотел, чтобы для Java и JVM-языков были средства для создания легковесных приложений.


      Так уже все есть. Просто мало кому это надо на самом деле.
      — Например, в яве есть свой HttpServer. Не нужно добавлять никаких внешних tomcat, jetty, netty и прочих.
      — IoC — а так ли это надо? Я прекрасно уже 3 года обхожусь без и доволен. Непонятная хотелка привнесенная из энтерпрайз мира.
      — nginx — совершенно не нужен. Если у Вас tomcat, netty, jetty — у них уже из коробки есть нативные биндинги на опенССЛ, точно такие же как и у енджинкса. А у нетти, например, даже к более быстрому форку опенССЛ — boringSSL. Опять же, старый костыль перекочевавший из прошлого, когда еще не было биндингов на яве.
      — шаблонизаторы? Уже вроде как 2017-й…
      • 0
        1. HttpServer лежит в com.sun, которого вроде нет в compact1.
        2. IoC иногда полезно чтобы код был более читаемый. Но пока действительно без него приходится
        3. nginx — нужен для SSL termination и отдаче статики. В простых http серверах нет поддержки openssl. Потом опять же есть неплохая интеграция nginx и letsencrypt. Я не смотрел на netty. Может быть он и может работать в compact1. Статику прогружать через heap тоже не хотелось бы, так как это будет влиять на gc и основные бэкэнд фичи
        4. Выбор шаблонизаторов скорее был не из-за технологий, а из-за требований к стабильности. Например, основные javascript технологии очень быстро меняются и не обратно совместимы. И каждый год появляется какая то новая модная технология. Задача же проекта не в создании модного web ui, а в backend декодировании сигналов.


        Конечно, если какой-нибудь angular/reactjs разработчик присоединиться и будет пилить web ui, то никто не будет против :)
        • 0
          Потом опять же есть неплохая интеграция nginx и letsencrypt.


          В нетти тоже. За другие серверы не скажу. Мне даже не надо стопать нетти, чтобы обновить сертификаты. В отличии от енджинкса.

          Статику прогружать через heap тоже не хотелось бы


          Опять же, в нетти не через хип. Уверен, что в других серверах так же. Хотя, да — для статики енджинкс наверное самое лучшее решение. Я отдаю файлы через нетти. Пока проблем не было. Лоад приличный.
          • 0
            Мне даже не надо стопать нетти, чтобы обновить сертификаты. В отличии от енджинкса.

            справедливости ради, для обновления сертификатов в nginx достаточно сделать
            service nginx reload
            
            и это будет абсолютно незаметно для клиентов.
            • 0
              и это будет абсолютно незаметно для клиентов.


              Для большинства приложений — да. Но если пользователи висят на keep-alive соединениях — то это будет заметно.
              • 0
                Естественно keep-alive, ведь речь о сертификатах, и соответственно HTTPS. Только что страшного произойдет, если клиент будет вынужден переустановить соединение?
          • 0
            Спасибо! Гляну нетти. 6мб может удастся съэкономить. Все таки подразумевается, что пользователь будет раз в год заходить на этот вэб ui, поэтому можно разочек и отдать файлы через jvm
            • 0
              Еще учтите, что если вы отдаете статику, то она прекрасно будет кешироватся в браузере (поэтому темплейты — фу-фу-фу :)). + если это система где один и тот же пользователь пользуется ею, то проблемы как бы и нету.

              Не уверен по поводу compact1 и нетти. Но вы уж гляньте и отпишитесь.
    • 0
      В этом плане очень хорош Go. Достаточно высокоуровневый язык, статические бинарики на выходе, довольно экономный с точки зрения памяти. И если вдруг понадобится разработать какой-нибудь сервис, я выберу именно его. Но писать приятнее всё-таки на Java/Kotlin.

      А за писать приятнее, к сожалению, нужно платить.


      А ещё требует проприетарную JRE (я не доверяю Oracle).
      Оно почти на 100% с открытым исходным кодом.
    • 0
      А ещё требует проприетарную JRE (я не доверяю Oracle)

      Насколько я понимаю, очень зря, поскольку Oracle внедряет дополнительные оптимизации в свою версию JRE. Не самые свежие, но пруфы по Raspberry:
      • 0

        Вот странная штука. Шипелёв же говорил, что Оракл берёт OpenJDK и просто его билдит и всё.

        • +1
          Занудства ради: А можно цитату, где он говорит про «просто билдит»?
          • 0

            Я где-то слышал, где не помню. Но на занудство можно ответить только ещё большим занудством. Я отсмотрел нонстоп около пяти часов Шипилёва и нашёл. Вот он этот вопрос. Если вдруг ссылка с временем не работает — начинать смотреть ровно на 47 (сорок седьмой) минуте.

            • 0
              Благодарю!
    • 0
      А ещё требует проприетарную JRE (я не доверяю Oracle).

      Используйте сборки openjdk. Для RPi есть хорошая сборка azul zulu с поддержкой armhf.
  • 0
    gson. Зависимость на java.sql (!!!)

    Неудивительно, сериализуют java.sql типы. В теории можно раскидать по разным артифактам (+ конечно же иметь сводный, как обычно, для 99.999% юзеров).

    angular, reactjs — на ровном месте привносят десяток короткоживущих технологий.

    Текущий vue.js minified — 79.7кб
    https://github.com/vuejs/vue/blob/dev/dist/vue.min.js
    Можно затащить библиотеку по старинке, без всяких npm и вебпаков.

    Спасибо автору, было интересно.
    • 0

      Можно и в сторону preact глянуть, если есть ограничение по размерам библиотек.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.