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

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

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

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

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

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

  • А может без базы? Для моих целей вполне подходят обычные файлы. 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 (надеюсь пароли нигде там не закоммитил)