image

Всем привет!

Закончилась одна из самых хардкорных конференций по Java – JPoint 2019, она проходила в седьмой раз и как всегда побила рекорд по посещаемости, в этот раз мероприятие привлекло более 1700 специалистов в области Java-разработки.

«Одноклассники» принимали участие во всех конференциях JPoint. Начиная с 2013 мы активно поддерживаем JPoint и на своих стендах устраиваем для участников различные активности по проверке знаний Java. В этом году у нас были знаменитые «нерешаемые» задачи от ведущих разработчиков OK.ru. Участники конференции, правильно ответившие на вопросы, получили призы.

Справедливости ради надо сказать, что из 600 листочков с задачами, которые мы раздали, обратно было получено менее 100, средний балл равен примерно 0.25.

Лучшим оказалось решение, набравшее 4 балла из 5 возможных.

Мы публикуем задачи и их решения, чтобы вы смогли проверить свои силы.

Битам быть


Эту задачу решили 40%, сдавших ответы.

Михаил создаёт потокобезопасный аналог BitSet. Допишите реализацию метода setBit().

Для простоты можно считать размер BitSet постоянным.

public class ConcurrentBitSet {
    private final AtomicLongArray bits;
 
    public ConcurrentBitSet(int size) {
        assert size >= 0;
        int words = (size + 63) / 64;
        bits = new AtomicLongArray(words);
    }
 
    public void setBit(int index) {
        // TODO: Implement me!
    }
}

Решение
Реализация с помощью updateAndGet()/getAndUpdate(), доступных с Java 8, может выглядеть так:

public void setBit(int index) {
    int word = index >> 6;
    long mask = 1L << index;
    bits.updateAndGet(word, value -> value | mask);
}

Аналогично выглядит реализация на старом-добром compareAndSet():

public void setBit(int index) {
    int word = index >> 6;
    long mask = 1L << index;
    long oldValue;
    long newValue;
    do {
        oldValue = bits.get(word);
        newValue = oldValue | mask;
    } while (!bits.compareAndSet(word, oldValue, newValue));
}


Enum уже не тот


Эту задачу решили 45%, сдавших ответы.

Татьяна хочет проверить, являются ли два объекта константами одного и того же enum. Что она не учла?

boolean sameEnum(Object o1, Object o2) {
    return o1.getClass().isEnum() &&
            o1.getClass() == o2.getClass();
}

Решение
Подсказка кроется в документации к методу Enum.getDeclaringClass(), который используется, например, в Enum.compareTo():

public final Class<E> getDeclaringClass() {
    Class<?> clazz = getClass();
    Class<?> zuper = clazz.getSuperclass();
    return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}

Для enum-констант с непустыми телами создаются промежуточные классы, поэтому правильный ответ может выглядеть так:

boolean sameEnum(Object o1, Object o2) {
    return o1 instanceof Enum &&
            o2 instanceof Enum &&
            ((Enum) o1).getDeclaringClass() == ((Enum) o2).getDeclaringClass();
}


Некомпилируемые связи


Эту задачу решили 42%, сдавших ответы.

Имеется следующий интерфейс:

interface Link<T> {
    T next();
}

Измените сигнатуру (но не тело) метода getTail(), чтобы код компилировался без ошибок и предупреждений.

Link getTail(Link head) {
    if (head.next() == null) {
        return head;
    }
    return getTail(head.next());
}

Решение
Правильных минимальных ответов всего три:

<T extends Link<T>> Link<T> getTail(Link<T> head)
<T extends Link<T>> Link<T> getTail(T head)
<T extends Link<T>> T getTail(T head)

Как это ни парадоксально выглядит, такая сигнатура не по зубам компилятору Java:

<T extends Link<T>> T getTail(Link<T> head)


Мессенджер


Эту задачу решили 14%, сдавших ответы.

Костя разрабатывает приложение для обмена сообщениями. Укажите ошибки в методе для отправки сообщения по сети.

void send(SocketChannel ch, String message) throws IOException {
    byte[] bytes = message.getBytes();
 
    ByteBuffer header = ByteBuffer.allocate(4);
 
    header.putInt(bytes.length);
    ch.write(header);
    ch.write(ByteBuffer.wrap(bytes));
}

Решение
В этом коде имеются как минимум три ошибки:


Так может выглядеть исправленная версия:

void send(SocketChannel ch, String message) throws IOException {
    byte[] bytes = message.getBytes(StandardCharsets.UTF_8);

    ByteBuffer header = ByteBuffer.allocate(4);
    header.putInt(bytes.length);
    header.flip();

    while (header.hasRemaining()) {
        ch.write(header);
    }

    ByteBuffer body = ByteBuffer.wrap(bytes);
    while (body.hasRemaining()) {
        ch.write(body);
    }
}


Java в контейнере


Эту задачу решили 7.5%, сдавших ответы.

Какие параметры JVM следует прописать Алексею, чтобы не позволить ОС Linux убить Java-процесс из-за превышения лимита памяти, отведённого на контейнер?

  • -Xmx
  • -XX:MaxMetaspaceSize
  • -XX:ReservedCodeCacheSize
  • -XX:+UseContainerSupport
  • -XX:MaxRAMPercentage
  • Память JVM нельзя ограничить

Решение
Память, потребляемая Java-процессом, далеко не ограничивается только хипом, Metaspace и Code Cache. Многие другие структуры JVM также занимают память, причём не все из них регулируются настройками. Помимо виртуальной Java машины нативную память выделяет Java Class Library и пользовательский код посредством Direct ByteBuffers и Mapped ByteBuffers.

Параметр UseContainerSupport совместно с MaxRAMPercentage влияет лишь на размер хипа. Таким образом, нет гарантированного способа избежать превышения лимита только с помощью флагов JVM, и правильным ответом будет последний. Подробнее об использовании памяти Java процессом можно посмотреть в докладе Андрея Паньгина на Joker 2018 «Память Java процесса по полочкам».

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


  1. gnkoshelev
    10.04.2019 00:00

    Постоянно забываю про «новые» методы updateAndGet и др. и пишу по старинке, повторяя код из них.

    А последний вопрос я, видимо, неправильно понял, выбрав вариант с -XX:+UseContainerSupport.
    В случае с UseContainerSupport размер хипа будет выбираться исходя из ограничений контейнера, а не физических размеров оперативной памяти, поэтому хип гарантированно не выйдет за ограничение (с немалым таким запасом).

    К слову, и без всяких контейнеров OOM Killer может прийти за java-процессом. :)

    P.S. Спасибо за топовые задачки (и настолку ;)).


  1. Maccimo
    10.04.2019 23:33

    Спасибо за задачки.
    На фоне «у нас синтаксическая ошибка!», «а у нас сырые типы в 2019!» с соседних стендов очень контрастируют.

    Интересно, почему у «Мессенджера» такой низкий процент, неужели никто не использует NIO?