[Java] Собеседование Backend-Java-разработчика: вопросы и где искать ответы. Часть 1
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Когда-то я проходил серию собеседований на Backend-Java-разработчика и записывал вопросы себе на будущее, чтобы потом можно было пробежаться и освежить память. Подумалось, что, вероятно, данный сборник будет полезен не только мне, поэтому сдул с него пыль, набросал ответов и делюсь с сообществом. На оригинальность и исключительность не претендую: подобные статьи уже были и на Хабре, и много где ещё — в конце (во второй части) приведу список ссылок, чтобы шпаргалка была максимально полной.
Точно установить сложность всех вопросов не берусь — на разном уровне их потребуется раскрыть с различной степенью подробности. Я написал ответы где-то на плюс-минус middle, щедро приправив ссылками для дальнейших изысканий. На самые популярные вопросы сразу перенаправляю в источники с готовыми ответами. Заодно посмотрим по ссылкам в статье, насколько Хабр может помочь в подготовке к собесам.
Текста получилось много, поэтому пришлось разбить на две части. В первой поговорим про Java и Spring, а обо всём остальном — во второй. Вторая часть тут
TL;DR
SPL
GitHub-репозиторий с полной шпаргалкой тут, а Хабр всё ещё торт.
Вопросы
Java
1. Вопросы про Equals, hashcode и их связь с HashMap.
SPL
Опишите Контракт. Далее разговор переходит к устройству HashMap. Как устроена внутри? А происходит в случае возникновения коллизии? Назовите алгоритмические сложности поиска, чтения, удаления из элемента мапы. А что если ключ — это массив байтов? А может быть так, что мы положим элемент в мапу, а потом не найдем? Обсасывают бедную мапу со всех сторон. Самая популярная тема для обсуждения. Спрашивают все. Абсолютно все.
Контракт equals и hashcode:
- Для одного и того же объекта хэшкоды одинаковые.
- Если объекты равны по equals, то и хэшкоды одинаковые.
- Если же хэшкоды равны, то объекты могут быть не равны по equals (коллизия).
- Если хэшкоды разные, то и объекты разные.
В статье на Хабре это подробно разобрано, если кому-то покажется мало.
Про HashMap и вопросы по ним есть несколько отличных статей на Хабре (в картинках, с дополнениями из Java 8, а тут вопросы-ответы про коллекциям). Кроме того, можно посмотреть исходный код в вашей любимой IDE. Можете сделать себе конспект и повесить на стену :)
2. Вопросы про списки: какие есть, алгоритмическая сложность, какой брать для вставки в середину, в конец, в огурец.
SPL
По сути это вопрос про ArrayList vs LinkedList. Опять же, заезженная пластинка, разобранная на Хабре — вопросы-ответы про коллекциям, ArrayList в картинках, LinkedList в картинках, Что «под капотом» у LinkedList. Посмотреть исходники тоже полезно. Например, можно понтануться тем, что вставка в середину в ArrayList выполняется с помощью нативно реализованной функции System.arraycopy, поэтому не всё так плохо, как могло бы быть в этом случае.
3. Перечислите методы класса `Object`.
SPL
Этот вопрос далее перетекает либо в обсуждение HashMap, либо в основы многопоточного программирования на Java.
Чтобы вы вдруг внезапно не забыли каких-то методов (как это сделал я :D), привожу вам список и ссылку на JavaDoc:
- clone
- equals
- finalize (Deprecated)
- getClass
- hashCode
- toString
- notify
- notifyAll
- wait
Также можно почитать, что там вообще есть в исходниках Object в статье на Хабре.
4. Расскажите про методы `wait`, `notify`, `notifyAll` и ключевое слово `synchronized`.
SPL
В принципе, статьи на Baeldung должно хватить. Лучше, конечно, пописать код с использованием wait, notify, notifyAll и synchronized руками. Также можно почитать официальный туториал от Oracle по Concurrency в Java.
Но если хотите пойти глубже, то хаброписатели опять спешат на помощь — тут. А также Java Language Specification, раздел 17.1 и 17.2.
5. JMM. Зачем нужно volatile. Популярный вопрос.
SPL
Не знаю как у вас, но у меня при упоминании JMM молниеносно всплывает в голове Алексей Шипилёв и его доклады — раз, два, три. Если вы больше чтец, чем смотрец, то Алексея можно и почитать — ать, два.
Кроме того, абсолютно не будет лишним посмотреть доклад Романа Елизарова по теоретическому минимуму JMM.
Если совсем нет времени, то можно пробежаться по небольшой статейке по JMM. Если есть время и интерес, тогда углубляемся в тему через статью на Хабре. А ещё на Хабре есть неплохой перевод статьи "Многопоточность. Java-модель памяти": часть 1 и часть 2.
Несомненным источником истины является Java Language Specification, раздел 17.4.
Также ответ на этот вопрос можно прочитать на itsobes.ru.
Не лишним будет ознакомиться с вопросом на JVM-уровне в статье How ‘volatile’ works on JVM level? на Medium.
6. Сборка мусора. Как работает? Какие сборщики знаете? Какие есть области памяти в JVM? Что будет с двумя или более объектами, которые ссылаю
SPL
Память в Java делится на Stack и Heap.
Stack — это область памяти, доступ к которой организован в порядке LIFO. Сюда помещается frame — локальные переменные и параметры вызываемого метода. Здесь можно сразу уточнить, что примитивы хранятся на стеке, а вот у объектов тут хранится только ссылка, а сами объекты в Heap. НО, благодаря Escape Analysis и скаляризации из Java 6, объекты, которые являются исключительно локальными и не возвращаются за пределы выполняемого метода, также сохраняются в стеке. Про Escape Analysis и скаляризацию есть доклад (видео или текст) Руслана Черемина, или ещё тут.
Frame создаётся и кладётся на Stack при вызове метода. Frame уничтожается, когда завершается его вызов метода, как в случае нормального завершения, так и в результате выброса неперехваченного исключения. У каждого потока есть свой Stack и он имеет ограниченный размер. Подробности можно посмотреть в JVM Specification.
Теперь про Heap и сборку мусора. Тут большинство просто хочет услышать то, что написано в одном из сообщений telegram-канала Senior's Blog. Процитирую основную часть здесь:
Heap делится на два поколения:
- Young Generation
- Eden
- Survivor 0 и Survivor 1
- Old Generation
- Tenured
Young разделен на три части: Eden, Survivor 0 и Survivor 1. В Eden создаются все новые объекты. Один из Survivor регионов всегда пустой. При полном заполнении региона Eden запускается малая сборка мусора, и все живые объекты из Eden и Survivor перемещаются в пустой Survivor, а Eden и использующийся Survivor полностью очищается. Это делается для уменьшения фрагментации памяти. Объекты, которые несколько раз перемещаются между Survivor, затем помещаются в Tenured.
В случае, когда места для новых объектов не хватает уже в Tenured, в дело вступает полная сборка мусора, работающая с объектами из обоих поколений. При этом старшее поколение не делится на подрегионы по аналогии с младшим, а представляет собой один большой кусок памяти. Поэтому после удаления мертвых объектов из Tenured производится не перенос данных (переносить уже некуда), а их уплотнение, то есть размещение последовательно, без фрагментации. Такой механизм очистки называется Mark-Sweep-Compact по названию его шагов (пометить выжившие объекты, очистить память от мертвых объектов, уплотнить выжившие объекты).
Бывают еще объекты-акселераты, размер которых настолько велик, что создавать их в Eden, а потом таскать за собой по Survivor’ам слишком накладно. В этом случае они размещаются сразу в Tenured.
Младшее поколение занимает одну треть всей кучи, а старшее, соответственно, две трети. При этом каждый регион Survivor занимает одну десятую младшего поколения, то есть Eden занимает восемь десятых.
Существуют следующие реализации GC:
- Serial Garbage Collector
- Parallel Garbage Collector. По умолчанию в Java 8.
- Concurrent Mark Sweep (CMS). Deprecated с Java 9.
- Garbage-First (G1). По умолчанию с Java 9. Есть видео от Владимира Иванова. Ещё можно почитать о G1 в туториале по настройке от Oracle.
- Z Garbage Collector (ZGC)
- Shenandoah Garbage Collector. Есть в наличии с Java 12. Тут, конечно же, нужно смотреть доклады Алексея Шипилёва — раз, два
Если совсем кратко, то можно ознакомиться тут и вот тут.
Почитать на Хабре подробнее про сборку мусора в Java можно в серии статей "Дюк, вынеси мусор!" от alygin — раз, два, три.
Послушать про работу с памятью и сборщиках мусора можно в выпуске 74 подкаста Podlodka с Алексеем Шипилёвом в гостях. Обязательно загляните в полезные ссылки к выпуску.
Ещё можно вспомнить про:
- Method Area — область памяти с информацией о классах, включая статические поля. Одна на всю JVM.
- Program Counter (PC) Register — отдельный на каждый поток регистр для хранения адреса текущей выполняемой инструкции.
- Run-time Constant Pool — выделяется из Method Area для каждого класса или интерфейса. Грубо говоря, хранит литералы. Подробнее.
- Native Method Stack — собственно Stack для работы нативных методов.
Дополнительно про gc и саму JVM (ох, бохатая и животрепещущая тема):
- На богомерзком medium в картинках
- Перевод статьи Алексея Шипилёва на Хабре — Самодельный сборщик мусора для OpenJDK
- Отрывок из Java Garbage Collection Handbook про reachability algorithm
- Статейка на Википедии про Tracing garbage collection
- Доклад Simone Bordet про ZGC и Shenandoah
- JVM Anatomy Quarks — серия постов от Алексея Шипилёва про устройство JVM. Это просто клад, за который будут воевать пришельцы на постапокалиптическую Землю, чтобы разгадать, как работает эта чёртва шайтан-виртуал-машина и промышленный код почивших человеков.
- Understanding JVM Internals — вот прям неплохо и с картинками.
7. Что такое Executor и ExecutorService, Thread pool и зачем нужны?
SPL
Создавать и убивать потоки — дорого. Давайте создадим N потоков (Thread pool) и будем их переиспользовать. А давайте. Вот тут описано развёрнуто.
Executor (void execute(Runnable command) — вот и весь интерфейс) и ExecutorService (уже покруче, может запускать Callable и не только) — грубо говоря, интерфейсы выполняторов параллельных задач. А реализуют их различные выполняторы на пулах потоков. Экземпляры готовых конкретных выполняторов можно получить с помощью класса Executors. Если смелый-умелый и зачем-то надо, то можно и самому реализовать, конечно.
Также подробнее можно почитать:
8. Могут ли быть в Java утечки памяти и когда? Как обнаружить причину? Как снять heap-dump?
SPL
Могут. Профилировать. Снимать heap-dump, например с помощью jmap, загружать в memory profiler (например в VisualVM)
Подробнее:
- Доступно изложено на Baeldung или то же самое тут, но на языке родных осин.
- Ещё тут
- здесь
- Старенькая статья на Хабре про типичные случаи утечки памяти в Java
- Диагностика утечек памяти в Java на Хабре
- Ищем утечки памяти с помощью Eclipse MAT на Хабре
- Устранение утечек памяти посредством слабых ссылок
- Устранение утечек памяти посредством гибких ссылок
- Бывают ли в Java утечки памяти?
- Диагностика OutOfMemoryError подручными средствами
- Java VisualVM — Browsing a Heap Dump
- VisualVM: мониторинг, профилировка и диагностика Java-приложений
- Доклад Андрея Паньгина Всё, что вы хотели знать о стек-трейсах и хип-дампах
- Different Ways to Capture Java Heap Dumps
- Analyze memory snapshots с помощью IntelliJ IDEA
- Analyze objects in the JVM heap с помощью IntelliJ IDEA
9. Что внутри параллельных стримов? На каком пуле работают параллельные стримы и в чем его особенность?
SPL
По умолчанию parallel stream использует ForkJoinPool.commonPool размером Runtime.getRuntime().availableProcessors() — 1. Common pool создаётся статически при первом обращении к ForkJoinPool и живёт до System::exit (игнорирует shutdown() или shutdownNow()). Когда некий поток отправляет задачу в common pool, то pool может использовать его же в качестве воркера. Common pool один на всё приложение. Можно запустить stream на отдельном ForkJoinPool — завернуть параллельный stream в Callable и передать на вход методу submit созданного ForkJoinPool. Этот трюк работает благодаря методу fork() из ForkJoinPool (тут подробности).
Сам по себе ForkJoinPool представляет реализацию ExecutorService, выполняющую ForkJoinTask (RecursiveAction и RecursiveTask). Данный pool создан для упрощения распараллеливания рекурсивных задач и утилизации породивших подзадачу потоков. ForkJoinPool использует подход work stealing — у каждого потока есть его локальная очередь задач, из хвоста которой другие потоки могут тырить себе задачи, если у них закончились свои. Украденная задача делится и заполняет очередь задач потока.
Подробнее:
- В статьях Stream API & ForkJoinPool и Fork/Join Framework в Java 7 на Хабре
- Посмотреть доклад Алексея Шипилёва ForkJoinPool в Java 8
- В статьях Guide to the Fork/Join Framework in Java и Guide to Work Stealing in Java на Baeldung
- JavaDoc к ForkJoinPool
- В статье Think Twice Before Using Java 8 Parallel Streams на DZone
- В статье Java Parallel Streams Are Bad for Your Health! в блоге JRebel
- С примерами и картинками — Java Parallel Stream
- С графиками в How does the Fork/Join framework act under different configurations?
- Как работают параллельные стримы?
10. Какие бывают операции в стримах? Напишите стрим?
SPL
Есть 2 вида операций в Java Stream:
- Промежуточные (Intermediate) — filter, map, sorted, peek и т.д. Возвращают Stream.
- Терминальные (Terminal) — collect, forEach, count, reduce, findFirst, anyMatch и т.д. Возвращают результат стрима и запускают его выполнение.
Кроме того, будет полезно ознакомиться с содержимым пакета java.util.stream и доступными коллекторами из Collectors.
Периодически просят написать какой-нибудь стрим, поэтому хорошо бы попрактиковаться. Можно на работе наесться, можно придумать задачи самому себе, можно поискать что-нибудь готовое:
- Java8 Code Kata
- Experience-Java-8
- Может быть даже курс — Java. Functional programming
Почитать подробнее про стримы лучше в Java Doc, но можно и в статьях:
- Java 8 Stream API
- The Java 8 Stream API Tutorial
- Полное руководство по Java 8 Stream API в картинках и примерах. Тут не просто в картинках, а в анимациях!
- Шпаргалка Java программиста 4. Java Stream API
- Java Stream API: что делает хорошо, а что не очень
- Пишем свой Spliterator
Посмотреть:
- На letsCode — Java Stream API: функционально, модно, молодёжно!
- Лекция в CSCenter от Тагира Валеева — Лекция 8. Stream API
- Доклад Тагира Валеева на Joker 2016 — Причуды Stream API
11. Что можно положить и достать из List<? extends Number>, а что с List<? super Number>? Что такое ковариантность, контрвариантность, инвариантность?
SPL
Тут речь пойдёт про PECS — Producer extends, Consumer super (Joshua Bloch, Effective Java). А также вариантность — перенос наследования исходных типов на производные от них типы (контейнеры, делегаты, обобщения).
Ковариантность (covariance) — перенос наследования исходных типов на производные от них типы в прямом порядке.
Переменной типа List<? extends T> разрешено присвоить экземпляр списка, параметризованного T или его подклассом, но не родительским классом. В список типа List<? extends T> нельзя добавить никакой объект (можно только null) — нельзя гарантировать какого именно типа экземпляр списка будет присвоен переменной, поэтому нельзя гарантировать, что добавляемый объект разрешён в таком списке. Однако, из списка можно прочитать объект и он будет типа T и экземпляром либо T, либо одного из подклассов T.
Соответственно, List<? extends Number> можно присвоить ArrayList<Number> или ArrayList<Integer>, но не ArrayList<Object>. Метод get возвращает Number, за которым может скрываться экземпляр Integer или другого наследника Number.
Массивы также ковариантны.
Переопределение методов, начиная с Java 5, ковариантно относительно типа результата и исключений.
List<?> аналогичен List<? extends Object> со всеми вытекающими.
Контрвариантность (contravariance) — перенос наследования исходных типов на производные от них типы в обратном порядке.
Переменной типа List<? super T> разрешено присвоить экземпляр списка, параметризованного T или его родительским классом, но не его подклассом. В список типа List<? super T> можно добавить экземпляр T или его подкласса, но нельзя добавить экземпляр родительских для T классов. Из такого списка с гарантией можно прочитать только Object, за которым может скрываться неизвестно какой его подкласс.
Соответственно, List<? super Number> можно присвоить либо ArrayList<Number>, либо ArrayList<Object>, но не список наследников Number(т.е. никаких ArrayList<Integer>). Можно добавить экземпляр Integer или Double (можно было бы Number, но он абстрактный), но нельзя — Object. Метод get возвращает Object — точнее сказать нельзя.
Инвариантность — наследование исходных типов не переносится на производные.
Переменной типа List<T> разрешено присвоить экземпляр списка, параметризованного только T. В список можно добавить экземпляр T или его подкласса. Список возвращает T, за которым может скрываться экземпляр его подкласса.
Соответственно, List<Number> можно присвоить ArrayList<Number>, но не ArrayList<Integer> или ArrayList<Object>. Можно добавить экземпляр Integer или Double (можно было бы Number, но он абстрактный), но нельзя — Object. Метод get возвращает Number, за которым может скрываться экземпляр Integer или другого наследника Number.
Подробнее:
- На Хабре: Погружаемся в Generics, Используем в API, изучаем вариантность в программировании
- Посмотреть доклад Александра Маторина Неочевидные Дженерики
- В одном из ответов на вопрос Generics FAQ
- Как ограничивается тип generic параметра?
- Что такое ковариантность и контравариантность?
- В одном из объяснений на StackOverflow: раз, два, три
- Ковариантность и контравариантность с точки зрения математики, теории категорий и программирования
- Ковариантность и контравариантность в Wikipedia
- Wildcards в официальном туториале Oracle
12. Как работает ConcurrentHashMap?
SPL
ConcurrentHashMap — это потокобезопасная мапа (карта, словарь, ассоциативный массив, но тут и далее просто "мапа"), у которой отсутствуют блокировки на всю мапу целиком.
Особенности реализации:
- Поля элемента мапы (Node<K,V>) val (значение) и next(следующее значение по данному ключу в цепочке или дереве), а также таблица бакетов (Node<K,V>[] table) объявлены как volatile
- Для операций вставки первого элемента в бакет используется CAS — алгоритм, а для других операций обновления в этой корзине (insert, delete, replace) блокировки
- Каждый бакет может блокироваться независимо путём блокировки первого элемента в корзине
- Таблице бакетов требуется volatile/atomic чтения, запись и CAS, поэтому используются intrinsics-операции (jdk.internal.misc.Unsafe)
- Concurrent resizing таблицы бакетов
- Ленивая инициализация таблицы бакетов
- При подсчёте количества элементов используется специальная реализация LongAdder
В результате имеем:
- Извлечение значения возвращает последний результат завершенного обновления мапы на момент начала извлечения. Или перефразируя, любой non-null результат, возвращаемый get(key) связан отношением happens-before со вставкой или обновлением по этому ключу
- Итераторы по ConcurrentHashMap возвращают элементы отображающие состояние мапы на определённый момент времени — они не бросают ConcurrentModificationException, но предназначены для использования одним потоком одновременно
- Нельзя полагаться на точность агрегирующих методов (size, isEmpty, containsValue), если мапа подвергается изменениям в разных потоках
- Не позволяет использовать null, который однозначно воспринимается как отсутствие значения
- Поддерживает потокобезопасные, затрагивающие все (или многие) элементы мапы, операции — forEach, search, reduce (bulk operations). Данные операции принимают на вход функции, которые не должны полагаться на какой-либо порядок элементов в мапе и в идеале должны быть чистыми (за исключением forEach). На вход данные операции также принимают parallelismThreshold — операции будут выполняться последовательно, если текущий размер мапы меньше parallelismThreshold. Значение Long.MAX_VALUE сделает операцию точно последовательной. Значение 1 максимизирует параллелизм и утилизацию ForkJoinPool.commonPool(), который будет использоваться для параллельных вычислений
На Хабре есть несколько устаревшая статья — будьте внимательны и осторожны с java 8 произошли изменения. Класс Segment<K,V> максимально урезан и сохранён только для обратной совместимости при сериализации, где и используется. concurrencyLevel также оставлен лишь для обратной совместимости и теперь служит в конструкторе только для увеличения initialCapacity до количества предполагаемых потоков-потребителей мапы:
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
Есть более современная статья с примером реализации ConcurrentMap. Также можно почитать гайд по ConcurrentMap на Baeldung.
13. Что такое Xmx и Xms, Xss?
SPL
JVM стартует с Xms количеством выделенной под heap памяти и максимально может увеличить её до значения Xmx.
Xss флаг определяет размер выделенной под стек памяти.
Общий вид:
java -Xmx<количество><единица измерения>
Можно использовать различные единицы измерения, например килобайты (k), мегабайты (m) или гигабайты (g).
Пример:
java -jar my.jar -Xms256m -Xmx2048m
Подробнее:
14. Как работают Атомики?
SPL
Атомарная операция — это операция, которая выполняется полностью или не выполняется совсем, частичное выполнение невозможно.
Атомики — это классы, которые выполняют операции изменения своего значения атомарно, т.о. они поддерживают lock-free thread-safe использование переменных. Достигается это с помощью алгоритма compare-and-swap (CAS) и работает быстрее, чем аналогичные реализации с блокировками. На уровне инструкций большинства процессоров имеется поддержка CAS.
В общем случае работу Атомиков можно описать следующим образом. Атомик хранит некоторое volatile значение value, для изменения которого используется метод compareAndSet(current, new), поэтому предварительно читается текущее значение — current. Данный метод с помощью CAS изменяет значение value только в том случае, если оно равно ожидаемому значению (т.е. current), прочитанному перед запуском compareAndSet(current, new). Если значение value было изменено в другом потоке, то оно не будет равно ожидаемому. Следовательно, метод compareAndSet вернет значение false. Поэтому следует повторять попытки чтения текущего значения и запуска с ним метода compareAndSet(current, new) пока current не будет равен value.
Условно можно разделить методы Атомиков на:
- compare-and-set — принимают current на вход и делают одну попытку записи через CAS
- set-and-get — самостоятельно читают current и пытаются изменить значение с помощью CAS в цикле, как описано выше
Непосредственно изменение значения value делегируется либо VarHandle, либо Unsafe, которые в свою очередь выполняют его на нативном уровне. VarHandle — это динамически сильно типизированная ссылка на переменную или на параметрически определяемое семейство переменных, включающее статические поля, нестатические поля, элементы массива или компоненты структуры данных нестандартного типа. Доступ к таким переменным поддерживается в различных режимах, включая простой доступ на чтение/запись, volotile доступ на чтение/запись и доступ на compare-and-swap.
В java.util.concurrent.atomic имеется следующий набор атомиков:
- AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray — представляют атомарные целочисленные, булевы примитивные типы, а также два массива атомарных целых чисел.
- AtomicReference — класс для атомарных операций со ссылкой на объект.
- AtomicMarkableReference — класс для атомарных операций над парой [reference, boolean].
- AtomicStampedReference — класс для атомарных операций над парой [reference, int].
- AtomicReferenceArray — массив атомарных ссылок
- AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater — классы для атомарного обновления полей по их именам через reflection.
- DoubleAccumulator, LongAccumulator — классы, представляющие атомарные аккумуляторы, которые принимают на вход чистую функцию-аккумулятор (BinaryOperator) и начальное значение. Сохраняет весь набор операндов, а когда необходимо получить значение, то аккумулирует их с помощью функции-аккумулятора. Порядок операндов и применения функции-аккумулятора не гарантируется. Используется, когда записей намного больше, чем чтения.
- DoubleAdder, LongAdder — классы, представляющие атомарные счётчики. Являются частным случаем атомарных аккумуляторов, у которых функция-аккумулятор выполняет простое суммирование, а начальным значением является 0.
С помощью атомиков можно реализовать блокировку, например так:
public class NonReentrantSpinLock {
private AtomicReference<Thread> owner = new AtomicReference<>();
public void lock() {
Thread currentThread = Thread.currentThread();
while (!owner.compareAndSet(null, currentThread)) {}
}
public void unlock() {
Thread currentThread = Thread.currentThread();
owner.compareAndSet(currentThread, null);
}
}
Подробнее:
- Как устроены атомики?
- Compare and Swap
- Обзор java.util.concurrent.* на Хабре
- Разбор основных концепций параллелизма на Хабре
- Книга "Java Concurrency на практике" — её отрывок на Хабре
- JDK concurrent package на Хабре
- Atomic operations на Хабре
- Concurrency: 6 способов жить с shared state на Хабре
- The Art of Multiprocessor Programming
- The JSR-133 Cookbook for Compiler Writers
- AtomicReference: A (Sometimes Easier) Alternative to Synchronized Blocks
- An Introduction to Atomic Variables in Java на Bealdung
- Переход к атомарности
- Use AtomicReference to implement Reentrant Lock
- A comprehensive understanding of Java atomic variable classes
- Faster Atomic*FieldUpdaters for Everyone
- Алексей Шипилёв — Если не Unsafe, то кто: восход VarHandles
- Introduction to nonblocking algorithms
15. Что внутри и как работают TreeSet/TreeMap? В чем идея Красно-черного дерева?
SPL
TreeMap — реализация NavigableMap, основанная на красно-чёрном дереве. Элементы отсортированы по ключам в натуральном порядке или с помощью Comparator, указанного при создании мапы, в зависимости от использовавшегося конструктора. Гарантирует логарифмическое время выполнения методов containsKey, get, put и remove.
TreeSet — реализация NavigableSet, основанная на TreeMap. Элементы отсортированы в натуральном порядке или с помощью Comparator, указанного при создании множества, в зависимости от использовавшегося конструктора. Гарантирует логарифмическое время выполнения методов add, contains и remove.
Обе коллекции НЕ synchronized и итератор по ним может выбросить ConcurrentModificationException.
Если в эти коллекции при использовании натурального порядка сортировки в качестве ключа попытаться положить null, то получим NullPointerException. В случае с компаратором поведение с null будет зависеть от реализации компаратора. До 7-й Java с добавлением null в TreeMap и TreeSet был баг.
Самая важная особенность красно-чёрного дерева в том, что оно умеет само себя балансировать, поэтому не важно в каком порядке будут добавляться в него элементы, преимущества этой структуры данных будут сохраняться. Сбалансированность достигается за счёт поддержания правил красно-чёрной раскраски вершин:
- Вершина может быть либо красной, либо чёрной и имеет двух потомков
- Красная вершина не может быть дочерней для красной вершины
- Количество чёрных вершин от корня до листа включительно одинаково для любого листа
- Корень дерева является чёрным
- Все листья — чёрные и не содержат данных
Подробнее:
- Статья про сбалансированные бинарные деревья на Хабре
- Java собеседование. Коллекции на Хабре
- Java TreeMap vs HashMap
- 10 TreeMap Java Interview Questions и TreeSet Interview Questions
- Internal Working of TreeMap in Java
- A Guide to TreeMap in Java и A Guide to TreeSet in Java на Bealdung
- Красно-черные деревья: коротко и ясно на Хабре
- Балансировка красно-чёрных деревьев — Три случая на Хабре
- Красно-чёрное дерево
- Визуализация красно-чёрного дерева. И ещё. А вот исходники
16. Что поменялось с Java 8 по Java <CURRENT_VERSION>?
SPL
Java имеет богатую историю. На данный момент проекты чаще всего разделяются на:
- legacy-проекты с версией Java меньше 8
- проекты на Java 8, самая распрастранённая и популярная
- проекты на Java 9+ (точнее либо 11 LTS, либо последние полугодовые релизы)
Между 8 и 9 версиями случился небольшой разлом с частичной потерей обратной совместимости, а потом приколы лицензирования подъехали, поэтому миграция и в без того консервативном мире Java-приложений идёт медленно. Однако идёт, и если вы собеседуетесь в компанию, где этот переход уже осуществили, то, вероятно, у вас поинтересуются, что же там с Java 8 поменялось, чем живёт и дышит современная Java.
На момент выхода статьи, имеем:
- 9: Project Jigsaw aka Модули, HTTP/2 Client (Incubator), jshell, G1 GC по умолчанию, Compact Strings и другие.
- 10: Local-Variable Type Inference (var), Parallel Full GC для G1, Graal можно использовать как основной JIT-компилятор и другие.
- 11 LTS: var в лямбдах, компиляция и запуск single-file программ через java, новые методы для String, Epsilon GC (Experimental), ZGC (Experimental) и другие.
- 12: Switch Expressions (Preview), Shenandoah (Experimental), улучшения в G1, JMH и другие
- 13: Text Blocks (Preview) и другое
- 14: Pattern Matching для instanceof (Preview), Packaging Tool (Incubator), улучшили сообщение для NullPointerExceptions, Records (Preview) и другие.
- 15: Sealed Classes (Preview), Hidden Classes, удаление Nashorn JavaScript Engine из JDK и другие.
Найти ссылки на документацию к API, языку и виртуальной машине, release notes и сравнить API между версиями можно в Java-альманахе.
Кроме всего прочего, есть ряд проектов, в рамках которых развиваются большие и ожидаемые сообществом изменения Java:
- Amber — проект по реализации маленьких, но продуктивных улучшений языка Java. В рамках данного проекта постепенно реализуется и независимо выходит целый набор JEP: var (JDK 10), Switch Expressions, Sealed Types, Records, Text Blocks, Pattern Matching для instanceof и другие.
- Panama — проект по улучшению взаимодействия между JVM и нативным кодом. На Хабре есть статья с разъяснениями и интервью с Владимиром Ивановым на эту тему.
- Loom — проект по внедрению в Java легковесных потоков. На Хабре есть две прекрасные статьи с разъяснениями: раз и два.
- Valhalla — это проект по созданию нескольких больших и сложных улучшений языка и VM. В него входят: Inline types, Generics over Primitive Types, Enhanced volatiles и другие возможные или необходимые в рамках проекта улучшения.
- Lanai — проект по улучшению рендеринга настольных Java-приложений на MacOS путём использования Metal Apple platform API. C 14 мая 2020 появились Early-Access сборки.
- и другие
Отдельно нужно упомянуть GraalVM — это JDK и виртуальная машина Java, которая создана, чтобы объединить необъединяемое:
- быстрое выполнение Java
- уменьшение времени старта и потребления памяти для Java
- комбинирование и исполнение программ, написанных на различных ЯП, в том числе на платформо-зависимых
- общие инструменты для всех ЯП
- поддержка JIT и AOT-компиляции
- и т.п.
Послушать на тему:
- Два выпуска подкаста Javaswag: раз и два
- Выпуск 172 Java подкаста Подлодка, в гости к которому пришёл Тагир Валеев
Почитать на Хабре:
- Руководство по возможностям Java версий 8-14
- API, ради которых наконец-то стоит обновиться с Java 8. Часть 1
- JAVA 9. Что нового?
- Обзор Java 9
- Модульность в Java 9
- Компактные строки в Java 9
- Java 10 General Availability
- Изменения в стандартной библиотеке Java 10
- Записки о миграции на Java 10
- Как Java 10 изменяет способ использования анонимных внутренних классов
- "Жизнь после Java 10": какие изменения принесет Java 11
- 90 новых фич (и API) в JDK 11
- Java 11: новое в String
- Java 11 / JDK 11: General Availability
- 39 новых фич, которые будут доступны в Java 12
- Пришло время Java 12! Обзор горячих JEP-ов
- Новое в Java 12: The Teeing Collector
- Только что вышла Java 13
- В Java 13 хотят добавить "текстовые блоки"
- Introducing Java 13: Let's dive Into JDK's New Features
- Что нового будет в Java 14
- Java 14 is coming
- Java 14: Record, более лаконичный instanceof, упаковщик jpackage, switch-лямбды и текстовые блоки
- Исследуем записи в Java 14
- Пробуем улучшенный оператор instanceof в Java 14
- Исследуем sealed классы в Java 15
- Sealed classes. Semantics vs performance
- Sealed типы в Java
- Что нового в Java 15?
- Вышла Java 15
- Project Panama: как сделать Java "ближе к железу"?
- Раздача халявы: нетормозящие треды в Java. Project Loom
- Project Loom: виртуальные потоки в Java уже близко
- Десять вещей, которые можно делать с GraalVM
- Как работает Graal — JIT-компилятор JVM на Java
- Graal: как использовать новый JIT-компилятор JVM в реальной жизни
- Разрабатываем утилиту на GraalVM
- Скрещиваем ужа с ежом: OpenJDK-11 + GraalVM
- JavaScript, Java, какая теперь разница?
- Что под капотом компиляторных оптимизаций GraalVM?
И не только:
- Java-альманах
- State of Loom: часть 1 и часть 2
- GraalVM
Посмотреть:
- Cay Horstmann — Feature evolution in Java 13 and beyond
- Тагир Валеев — Java 9-14: Маленькие оптимизации
- Никита Липский — Java 9 Модули. Почему не OSGi?
- Cay Horstmann — Java 9: the good parts (not modules)
- Владимир Иванов — Project Panama: как сделать Java “ближе к железу”?
- Олег Чирухин — GraalVM Всемогущий
- Олег Чирухин — Graal, Value Types, Loom и прочие ништяки
- Олег Шелаев — Компилируем Java ahead of time с GraalVM
- Олег Шелаев — Суперкомпиляция, partial evaluation, проекции Футамуры и как GraalVM спасет мир
- Project Loom и Новое в JDK 14 на letsCode
- GOTO 2019 • Life After Java 8 • Trisha Gee
- Dalia Abo Sheasha — Migrating beyond Java 8
- Project Loom: Helping Write Concurrent Applications on the Java Platform by Ron Pressler
17. В какой кодировке строки в Java? Как хранятся строки внутри класса String? Как устроен String?
SPL
До Java 9 все строки имели кодировку UTF-16 (2 байта на символ) и хранились в массиве char.
С Java 9 пришло такое изменение как Compact String. Если все символы строки входят в множество символов Latin-1 (а это подавляющее большинство строк), то каждый из них может поместиться в 1 байт, поэтому в этом случае массив char избыточен. В результате было принято решение заменить массив char на массив byte, что позволяет строкам Latin-1 расходовать меньше памяти. Кодировка строки хранится в отдельном поле byte coder, значение которого представляет Latin-1 или UTF-16.
Также интересной особенностью является кеширование классом String своего hashcode.
Строки являются неизменяемыми, наследоваться от строк запрещено (final class). Все операции по изменении строки возвращают её новый экземпляр, в том числе и конкатенация строк. Компилятор умеет оптимизировать конкатенацию и превращать её в объект StringBuilder и совокупность вызовов методов append. ОДНАКО! В Java 9 вошёл JEP 280: Indify String Concatenation, который изменил эту оптимизацию и пошёл ещё дальше. Теперь вместо StringBuilder генерируется bytecode для вызова StringConcatFactory через invokedynamic, поэтому стоит расслабиться и чаще выбирать +.
Ещё можно упомянуть про String pool — это выделяемое в heap пространство, которое используется для оптимизации потребления памяти при хранении строк. Благодаря ему одинаковые строковые литералы могут ссылаться на один и тот же объект.
Стоит помнить, что с помощью [String.intern()](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/String.html#intern()) производительности особой не добиться, а можно наоборот пустить всё по миру. Лучше напишите свою реализацию. Подоробнее читайте в статье Алексея Шипилёва — JVM Anatomy Quark #10: String.intern().
Кроме того, equals и методы поиска (например indexOf) оптимизируются JIT компилятором на нативном уровне.
Посмотреть доклады Алексея Шипилёва на тему строк: Катехизис java.lang.String и The Lord of the Strings: Two Scours.
Подробнее:
- String javadoc
- Как обойти строчку?
- Из чего состоит String?
- JDK 9/JEP 280: конкатенация строк никогда больше не будет прежней на Хабре
- Компактные строки в Java 9 на Хабре
- Guide to Java String Pool на Baeldung
- Compact Strings in Java 9
- Владимир Иванов — Глубокое погружение в invokedynamic
- Charles Nutter — Let's Talk About Invokedynamic
- Что там с JEP-303 или изобретаем invokedynamic
18. Что такое ThreadLocal переменные?
SPL
ThreadLocal — класс в виде обёртки для хранения отдельной независимой копии значения переменной для каждого использующего её потока, что позволяет сделать работу с такой переменной потокобезопасной.
Данные ThreadLocal-переменных хранятся не в них самих, а непосредственно в объектах Thread. У каждого экземпляра класса Thread есть поле ThreadLocal.ThreadLocalMap threadLocals, которое инициализируется и используется ThreadLocal. ThreadLocal.ThreadLocalMap представляет собой специализированную версию HashMap, записи которой наследуют от WeakReference<ThreadLocal<?>>, используя ключ мапы как ref field слабой ссылки. Ключами такой мапы являются ThreadLocal, а значением — Object. Если ключ записи равен null, то такая запись называется просроченной (stale) и будет удалена из мапы.
Следует обратить внимание, что ThreadLocal изолирует именно ссылки на объекты, а не копии их значений. Если изолированные внутри потоков ссылки ведут на один и тот же объект, то возможны коллизии.
Когда у ThreadLocal-переменной запрашивается её значение (например через метод get), то она получает текущий поток, извлекает из него мапу threadLocals, и получает значение из мапы, используя себя в качестве ключа. Аналогично выполняются методы изменения значения ThreadLocal.
Из этого следует, что значение ThreadLocal-переменной должно устанавливаться в том же потоке, в котором оно будет использоваться.
Подробнее:
19. Сколько в байт занимает каждый из примитивных типов в памяти? А объект?
SPL
Казалось бы:
- byte — 1 байт
- short — 2 байта
- int — 4 байта
- long — 8 байт
- char — 2 байта
- float — 4 байта
- double — 8 байт
А размер boolean не упоминается в спецификации вовсе. Однако также спецификация не запрещает использовать для хранения примитива больше памяти — главное, чтобы размер был достаточным для всех значений. Конкретный объём таки зависит от реализации JVM. Не последнюю роль в этом играет выравнивание данных в памяти.
Похожая ситуация и со ссылочными типами — спецификация JVM не требует какой-то определённой структуры для объектов и отдаёт её на откуп реализации. Все тонкости и секреты занимаемой объектами памяти раскрывает Алексей Шипилёв в своей статье Java Objects Inside Out.
Подробнее:
- The Java Virtual Machine Specification
- Какие существуют примитивы?
- Сколько памяти занимает объект?
- Какие существуют примитивы?
- Размер Java объектов на Хабре
- Java Objects Inside Out
- jol
- Как JVM аллоцирует объекты? на Хабре
- Сжатие указателей в Java на Хабре
- Measuring Object Sizes in the JVM на Bealdung
Если вас заинтересовало представление объектов в jvm и их реализация (и вы умеете-могёте читать C++), то можно пойти посмотреть исходники openjdk. Начать, например, отсюда:
20. Какие ссылки бывают в Java?
SPL
Типы ссылок в Java:
- Strong reference — обычная переменная ссылочного типа в Java. Объект такой ссылки очищается GC не раньше, чем станет неиспользуемым (никто нигде на него больше не ссылается).
- Слабые ссылки — сборщик мусора тем или иным образом не учитывает связь ссылки и объекта в куче при выявлении объектов, подлежащих удалению. Объект будет удалён даже при наличии слабой ссылки на него:
- Soft reference — мягкая ссылка, экземпляр класса SoftReference. Объект гарантированно будет собран GC до возникновения OutOfMemoryError. Может использоваться для реализации кэшей, увеличивающихся без риска OutOfMemoryError для приложения.
- Weak reference — слабая ссылка, экземпляр класса WeakReference. Не препятствует утилизации объекта и игнорируется GC при сборке мусора. Может использоваться для хранения некоторой связанной с объектом информации до момента его смерти. Также стоит обратить внимание на WeakHashMap.
- Phantom reference — фантомная ссылка, экземпляр класса PhantomReference. Не препятствует утилизации объекта и игнорируется GC при сборке мусора и имеет ряд особенностей, описанных ниже. Может быть применена для получения уведомления, что объект стал неиспользуемым и можно освободить связанные с ним ресурсы (как более надёжный вариант, чем finalize(), вызов которого не гарантируется, может проводить сеансы воскрешения и вообще deprecated).
Чтобы достать объект из слабых ссылок, необходимо вызывать метод get(). Если объект недостижим, то метод вернёт null. Для фантомных ссылок всегда возвращается null.
При создании слабой ссылки в конструктор можно, а для PhantomReference необходимо, передать экземпляр ReferenceQueue — в очереди будет сообщение, когда ссылка протухнет. Для SoftReference и WeakReference это будет ДО финализации объекта, а для PhantomReference ПОСЛЕ. Однако фактическое удаление объекта фантомной ссылки из памяти не производится до момента её очистки.
Подробнее:
Spring
21. Какие есть scope в Spring? Какой по умолчанию? Чем singleton отличается от prototype? Можно ли сделать свой scope и как? Плавно переходит в вопрос 'Как заи
SPL
Spring scope:
- singleton (по умолчанию)
- prototype
- request
- session
- application
- websocket
Про scope подробнее можно прочитать в документации, Bealdung. И, конечно же, надо посмотреть Spring-потрошитель Ч. 2.
Про prototype в singleton можно вспомнить несколько вариантов:
- @Lookup
- Фабрика для создания экземпляров prototype-бинов
- ProxyMod = ScopedProxyMode.TARGET_CLASS
Подробнее о каждом варианте есть в Bealdung и смотрим Spring-потрошитель Ч. 2.
22. Часто спрашивают о циклических зависимостях бинов в Spring. Проблема ли это или что получим в результате? Если проблема, то как её решить?
SPL
Да, это проблема — будет выброшено исключение BeanCurrentlyInCreationException (при внедрении зависимостей через конструктор).
Варианты решения:
- Ещё раз подумать, той ли дорогой мы держим путь — может не поздно сделать редизайн и избавиться от циклических зависимостей
- Инициализировать один из бинов лениво с помощью @Lazy
- Внедрение зависимостей в setter-метод, а не в конструктор
Подробнее есть в документации и в Bealdung
23. Бывают вопросы про жизненный цикл бина, этапы инициализации контекста, про устройство спринга внутри, про DI и как он работает
SPL
Тут однозначно надо смотреть Spring-потрошитель часть 1 и часть 2. Также благое дело — это почитать документацию.
Также по этапам инициализации контекста есть статья с красивыми картинками на хабре.
24. Расскажите про прокси и про @Transactional. Как работает и зачем? Какие могут быть проблемы? Можно ли навесить @Transactional на приватный метод? А ес
SPL
Для начала, если вы не знали или случайно забыли про паттерн Proxy в общем виде, то можно освежиться здесь.
Допустим, что наш сервис MyServiceImpl имеет 2 публичных метода, аннотированных @Transactional — method1 и method2(он с Propagation.REQUIRES_NEW). В method1 вызываем method2.
В связи с тем, что для поддержки транзакций через аннотации используется Spring AOP, в момент вызова method1() на самом деле вызывается метод прокси объекта. Создается новая транзакция и далее происходит вызов method1() класса MyServiceImpl. А когда из method1() вызовем method2(), обращения к прокси нет, вызывается уже сразу метод нашего класса и, соответственно, никаких новых транзакций создаваться не будет.
Это цитата и краткий ответ на вопрос из статьи на Хабре, где можно ознакомиться с подробностями.
Что тут можно ещё посоветовать? Spring-потрошитель опять и снова — часть 1 и часть 2. А также документация является несомненным и любимым первоисточником информации о Proxy и управление транзакциями.
25. Где у обычного (НЕ Boot) Spring-приложения main-класс?
SPL
Старое доброе обычное Spring-приложение деплоится в контейнер сервлетов (или сервер приложений), где и расположен main-класс. При этом оно собирается в war-архив. Когда war-файл разворачивается в контейнере, контейнер обычно распаковывает его для доступа к файлам, а затем запускает приложение. Spring Boot приложение также можно собрать как war и задеплоить его таким же образом.
Подробнее:
- Понимание WAR
- В чём разница между jar и war?
- Конвертация Spring Boot JAR приложения в WAR на RUS или ENG
26. Как работает Spring Boot и его стартеры?
SPL
Во-первых, благодаря spring-boot-starter-parent, у которого родителем является spring-boot-dependencies, можно особо не париться о зависимостях и их версиях — большинство версии того, что может потребоваться прописано и согласовано в dependencyManagement родительского pom. Или можно заимпортировать BOM.
Spring Boot черпает свою мощь из стартеров — наборов сконфигурированных бинов со всеми необходимыми зависимостями, готовых к использованию и доступных для тонкой настройки через properties-файлы.
Для Spring Boot приложения создаётся main-класс с аннотацией @SpringBootApplication и запуском метода run класса SpringApplication, который возвращает ApplicationContext.
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Аннотация @SpringBootApplication просто скрывает за собой аннотации @EnableAutoConfiguration, @ComponentScan и @Configuration.
SpringBootApplication создаёт либо WebApplicationContext (если в classpath есть Servlet и ConfigurableWebApplicationContext), либо GenericApplicationContext.
При создании стартера используется файл META-INF/spring.factories — в нём ключу org.springframework.boot.autoconfigure.EnableAutoConfiguration приравнивается список из полных имён всех классов-конфигураций (а в них бины, @ComponentScan, @Import и т.п.) стартера через запятую.
Аннотация @EnableAutoConfiguration импортирует AutoConfigurationImportSelector, который и отвечает за поиск необходимых классов конфигурации. Вызов метода getCandidateConfigurations обращается к SpringFactoriesLoader и его методу loadFactoryNames, чтобы просканировать classpath на наличие файлов META-INF/spring.factories и имен классов-конфигураций в них, а затем загрузить их в контекст.
Также у Spring boot есть модуль spring-boot-autoconfigure со своим файлом META-INF/spring.factories. Чтобы не создавать все-все бины из конфигураций, у бинов используется аннотация @Conditional (или её вариации) с каким-либо условием.
Чтобы упаковать Spring boot в jar используется spring-boot-maven-plugin. У такого jar в META-INF/MANIFEST.MF будет прописан Main-Class — org.springframework.boot.loader.JarLauncher, а в Start-Class будет уже main-класс нашего приложения. JarLauncher формирует class path (в начале в нём только org.springframework.boot), который находится в BOOT-INF(там lib с зависимостями и class с классами приложения), а затем запускает Start-Class.
Посмотреть:
- Доклад Евгения Борисова и Кирилла Толкачёва Boot yourself, Spring is coming: часть 1, часть 2. На Хабре есть текстовая расшифровка: часть 1, часть 2.
- Доклад Кирилла Толкачёва и Максима Гореликова Spring Boot Starter — как и зачем?
- Доклад Кирилла Толкачёва и Александра Тарасова — Твой личный Spring Boot Starter
Почитать:
- На Хабре: Как работает Spring Boot Auto-Configuration, Пишем свой spring-boot-starter и Использование Conditional в Spring
- Hа Baeldung: A Comparison Between Spring and Spring Boot, Create a Custom Auto-Configuration with Spring Boot, Intro to Spring Boot Starters, Spring Boot: Configuring a Main Class
- What is Spring Boot? Autoconfigurations In-Depth
- Spring Boot for beginners
- Spring Boot Documentation
- Список готовых стартеров
27. Как выполняется http-запрос в Spring?
SPL
В современном Spring есть два подхода к построению веб-приложений:
- Spring MVC
- Spring WebFlux
Работа Spring MVC строится вокруг DispatcherServlet, который является обычным Servlet'ом и реализует паттерн Front Controller: принимает Http-запросы и координирует их с требуемыми обработчиками. Для своей конфигурации DispatcherServlet использует WebApplicationContext. DispatcherServlet в обработке запроса помогают несколько "специальных бинов" следующим образом:
- После получения HTTP-запроса DispatcherServlet перебирает доступные ему (предварительно найденные в контексте) экземпляры HandlerMapping, один из которых определит, метод какого Controller должен быть вызван. Реализации HandlerMapping, использующиеся по умолчанию: BeanNameUrlHandlerMapping и RequestMappingHandlerMapping (создаёт экземпляры RequestMappingInfo по методам аннотированным @RequestMapping в классах с аннотацией @Controller). HandlerMapping по HttpServletRequest находит соответствующий обработчик — handler-объект (например, HandlerMethod). Каждый HandlerMapping может иметь несколько реализаций HandlerInterceptor — интерфейса для кастомизации пред- и постобработки запроса. Список из HandlerInterceptor'ов и handler-объекта образуют экземпляр класса HandlerExecutionChain, который возвращается в DispatcherServlet.
- Для выбранного обработчика определяется соответствующий HandlerAdapter из предварительно найденных в контексте. По умолчанию используются HttpRequestHandlerAdapter (поддерживает классы, реализующие интерфейс HttpRequestHandler), SimpleControllerHandlerAdapter (поддерживает классы, реализующие интерфейс Controller) или RequestMappingHandlerAdapter (поддерживает контроллеры с аннотацией @RequestMapping).
- Происходит вызов метода applyPreHandle объекта HandlerExecutionChain. Если он вернёт true, то значит все HandlerInterceptor выполнили свою предобработку и можно перейти к вызову основного обработчика. false будет означать, что один из HandlerInterceptor взял обработку ответа на себя в обход основного обработчика.
- Выбранный HandlerAdapter извлекается из HandlerExecutionChain и с помощью метода handle принимает объекты запроса и ответа, а также найденный метод-обработчик запроса.
- Метод-обработчик запроса из Controller (вызванный через handle) выполняется и возвращает в DispatcherServlet ModelAndView. При помощи интерфейса ViewResolver DispatcherServlet определяет, какой View нужно использовать на основании полученного имени.
Если мы имеем дело с REST-Controller или RESTful-методом контроллера, то вместо ModelAndView в DispatcherServlet из Controller вернётся null и, соответственно, никакой ViewResolver задействован не будет — ответ сразу будет полностью содержаться в теле HttpServletResponse после выполнения handle. Чтобы определить RESTful-методы, достаточно аннотировать их @ResponseBody либо вместо @Controller у класса поставить @RestController, если все методы котроллера будут RESTful.
- Перед завершением обработки запроса у объекта HandlerExecutionChain вызывается метод applyPostHandle для постобработки с помощью HandlerInterceptorов.
- Если в процессе обработки запроса выбрасывается исключение, то оно обрабатывается с помощью одной из реализаций интерфейса HandlerExceptionResolver. По умолчанию используются ExceptionHandlerExceptionResolver (обрабатывает исключени из методов, аннотированных @ExceptionHandler), ResponseStatusExceptionResolver (используется для отображения исключений аннотированных @ResponseStatus в коды HTTP-статусов) и DefaultHandlerExceptionResolver (отображает стандартные исключения Spring MVC в коды HTTP-статусов).
- В случае с классическим Controller после того, как View создан, DispatcherServlet отправляет данные в виде атрибутов в View, который в конечном итоге записывается в HttpServletResponse. Для REST-Controller ответ данная логика не вызывается, ведь ответ уже в HttpServletResponse.
Когда HTTP запрос приходит с указанным заголовком Accept, Spring MVC перебирает доступные HttpMessageConverter до тех пор, пока не найдет того, кто сможет конвертировать из типов POJO доменной модели в указанный тип заголовка Accept. HttpMessageConverter работает в обоих направлениях: тела входящих запросов конвертируются в Java объекты, а Java объекты конвертируются в тела HTTP ответов.
По умолчанию, Spring Boot определяет довольно обширный набор реализаций HttpMessageConverter, подходящие для использования широкого круга задач, но также можно добавить поддержку и для других форматов в виде собственной или сторонней реализации HttpMessageConverter или переопределить существующие.
Также стоит упомянуть, что как и в случае любого другого сервлета, к обработке запроса в Spring MVC может быть применена одна из реализаций интерфейса javax.servlet.Filter как до выполнения запроса, так и после. Sring MVC предоставляет несколько уже готовых реализаций.
Отдельного разговора заслуживает путь запроса по внутренностям Spring Security, где используется множество различных фильтров. На хабре есть статья об этом.
Подробнее:
- Spring MVC — основные принципы на Хабре
- Путь запроса по внутренностям Spring Security на Хабре
- An Intro to the Spring DispatcherServlet на Bealdung
- HandlerAdapters in Spring MVC на Bealdung
- Quick Guide to Spring Controllers на Bealdung
- Spring RequestMapping на Bealdung
- Http Message Converters with the Spring Framework на Bealdung
- How to Define a Spring Boot Filter? на Bealdung
- Spring Professional Study Notes
- Spring Security Architecture
- Схематично
Документация:
Spring WebFlux — это реактивный веб-фреймворк, который появился в Spring Framework 5.0. Он не требует Servlet API (но может использовать Servlet 3.1+containers, хотя чаще это Netty (по умолчанию в Spring Boot) или Undertow), полностью асинхронный и неблокирующий, реализует спецификацию Reactive Streams при помощи проекта Reactor.
В Spring WebFlux используется большинство аннотаций из Spring MVC (RestController, RequestMapping и другие) для определения аннотированных контроллеров. Однако представляет новую возможность создания функциональных котроллеров, основанных на HandlerFunction.
В Spring WebFlux обработка запроса на стороне сервера строится в два уровня:
- HttpHandler — это базовый интерфейс обработки HTTP-запросов с использованием неблокирующего I/O, Reactive Streams back pressure через адаптеры для Reactor Netty, Undertow и т.д.
- WebHandler — интерфейс, который предоставляет верхнеуровневое API для обработки HTTP-запросов поверх аннотированных или функциональных контроллеров.
Контракт HttpHandler представляет обработку HTTP-запроса, как его прохождение через цепочку множества WebExceptionHandler, множества WebFilter и одного единственного WebHandler. Сборкой цепочки занимается WebHttpHandlerBuilder при помощи ApplicationContext.
Диспетчеризация запросов в Spring WebFlux выполняется DispatcherHandler, который является имплементацией интерфейса WebHandler и также реализует паттерн Front Controller: принимает Http-запросы и координирует их с требуемыми обработчиками. DispatcherHandler — это Spring bean, имплементирующий ApplicationContextAware для доступа к контексту, с которым он был запущен. DispatcherHandler с бин-именем webHandler обнаруживает WebHttpHandlerBuilder и помещает в цепочку в качестве WebHandler.
DispatcherHandler в ходе обработки http-запроса и ответа делегирует часть работы "специальным бинам", которые могут быть подвержены кастомизации, расширению и замене пользователем. Сам процесс обработки выглядит следующим образом:
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
if (this.handlerMappings == null) {
return createNotFoundError();
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(createNotFoundError())
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
- Каждый из экземпляров HandlerMapping пытается найти подобающий обработчик для данного запроса (какой-то метод, какого-то контроллера). В итоге выбирается первый найденный обработчик (handler). Основными доступными реализациями HandlerMapping являются:
- RequestMappingHandlerMapping для методов-обработчиков, аннотированных @RequestMapping
- RouterFunctionMapping для функциональных обработчиков
- SimpleUrlHandlerMapping для маппинга URL-ов на бины-обработчики запросов
- Если обработчик найден, то для него (в invokeHandler) выбирается подходящий HandlerAdapter, вызывается его метод handle для непосредственной обработки запроса выбранным обработчиком. Результат обработки упаковывается в HandlerResult, который возвращается в DispatcherHandler. Главная задача HandlerAdapter — скрыть детали и способ непосредственного вызова метода-обработчика от DispatcherHandler. Примерами доступных реализаций HandlerAdapter являются:
- RequestMappingHandlerAdapter — для вызова методов, аннотированных @RequestMapping
- HandlerFunctionAdapter — для вызова HandlerFunctions
- Полученный HandlerResult обрабатывается (в handleResult) необходимым для него HandlerResultHandler. Здесь обработка завершается формированием ответа на запрос требуемым образом. По умолчанию доступно несколько реализаций HandlerResultHandler:
- ResponseEntityResultHandler — обрабатывает ResponseEntity, обычно из @Controller
- ServerResponseResultHandler — обрабатывает ServerResponse, обычно из функциональных контроллеров
- ResponseBodyResultHandler — обрабатывает возвращаемые значения из методов, аннотированных @ResponseBody, или методов класса @RestController
- ViewResolutionResultHandler — инкапсулирует в себе алгоритм View Resolution и обработку поддерживаемых данным алгоритмом типов результатов
Документация:
Продолжение следует
Во второй части поговорим о Hibernate, базах данных, паттернах и практиках разработки, об одной популярной библиотеке, поддержке и сопровождении наших приложений, а также посмотрим на альтернативные шпаргалки и подведём итоги.
===========
Источник:
habr.com
===========
Похожие новости:
- [Java] Ласточка в мире микросервисов
- [SaaS / S+S, Управление персоналом] Помощник в проведении технического интервью и совместный кодинг
- [Разработка веб-сайтов, JavaScript, Angular, ReactJS, TypeScript] Schedulers в RxJS
- [JavaScript] Делаем симулятор двухпозиционного регулятора на JavaScript
- [Разработка веб-сайтов, JavaScript, Node.JS, ООП, TypeScript] Внедрение зависимостей (dependency injection) через свойства-функции в JavaScript
- [Java] Уведомления от Bitbucket в Telegram
- [Open source, Виртуализация, Читальный зал, Openshift] 4 книги по цифровой трансформации для тимлидов, шпаргалка по Quarkus & Observability…
- [Разработка веб-сайтов, JavaScript, Программирование] JavaScript: что нас ждет в следующем году
- [Разработка веб-сайтов, JavaScript, HTML, Расширения для браузеров] Растянуть видео в браузере
- [Java, Kotlin, Конференции] Lamoda x Joker 2020
Теги для поиска: #_java, #_java, #_sobesedovanie (собеседование), #_voprosy_i_otvety (вопросы и ответы), #_spring, #_backend, #_shpargalka (шпаргалка), #_java
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:01
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Когда-то я проходил серию собеседований на Backend-Java-разработчика и записывал вопросы себе на будущее, чтобы потом можно было пробежаться и освежить память. Подумалось, что, вероятно, данный сборник будет полезен не только мне, поэтому сдул с него пыль, набросал ответов и делюсь с сообществом. На оригинальность и исключительность не претендую: подобные статьи уже были и на Хабре, и много где ещё — в конце (во второй части) приведу список ссылок, чтобы шпаргалка была максимально полной. Точно установить сложность всех вопросов не берусь — на разном уровне их потребуется раскрыть с различной степенью подробности. Я написал ответы где-то на плюс-минус middle, щедро приправив ссылками для дальнейших изысканий. На самые популярные вопросы сразу перенаправляю в источники с готовыми ответами. Заодно посмотрим по ссылкам в статье, насколько Хабр может помочь в подготовке к собесам. Текста получилось много, поэтому пришлось разбить на две части. В первой поговорим про Java и Spring, а обо всём остальном — во второй. Вторая часть тут TL;DRSPLGitHub-репозиторий с полной шпаргалкой тут, а Хабр всё ещё торт.
Вопросы Java 1. Вопросы про Equals, hashcode и их связь с HashMap.SPLОпишите Контракт. Далее разговор переходит к устройству HashMap. Как устроена внутри? А происходит в случае возникновения коллизии? Назовите алгоритмические сложности поиска, чтения, удаления из элемента мапы. А что если ключ — это массив байтов? А может быть так, что мы положим элемент в мапу, а потом не найдем? Обсасывают бедную мапу со всех сторон. Самая популярная тема для обсуждения. Спрашивают все. Абсолютно все.
Контракт equals и hashcode:
В статье на Хабре это подробно разобрано, если кому-то покажется мало. Про HashMap и вопросы по ним есть несколько отличных статей на Хабре (в картинках, с дополнениями из Java 8, а тут вопросы-ответы про коллекциям). Кроме того, можно посмотреть исходный код в вашей любимой IDE. Можете сделать себе конспект и повесить на стену :) 2. Вопросы про списки: какие есть, алгоритмическая сложность, какой брать для вставки в середину, в конец, в огурец.SPLПо сути это вопрос про ArrayList vs LinkedList. Опять же, заезженная пластинка, разобранная на Хабре — вопросы-ответы про коллекциям, ArrayList в картинках, LinkedList в картинках, Что «под капотом» у LinkedList. Посмотреть исходники тоже полезно. Например, можно понтануться тем, что вставка в середину в ArrayList выполняется с помощью нативно реализованной функции System.arraycopy, поэтому не всё так плохо, как могло бы быть в этом случае.
3. Перечислите методы класса `Object`.SPLЭтот вопрос далее перетекает либо в обсуждение HashMap, либо в основы многопоточного программирования на Java.
Чтобы вы вдруг внезапно не забыли каких-то методов (как это сделал я :D), привожу вам список и ссылку на JavaDoc:
Также можно почитать, что там вообще есть в исходниках Object в статье на Хабре. 4. Расскажите про методы `wait`, `notify`, `notifyAll` и ключевое слово `synchronized`.SPLВ принципе, статьи на Baeldung должно хватить. Лучше, конечно, пописать код с использованием wait, notify, notifyAll и synchronized руками. Также можно почитать официальный туториал от Oracle по Concurrency в Java.
Но если хотите пойти глубже, то хаброписатели опять спешат на помощь — тут. А также Java Language Specification, раздел 17.1 и 17.2. 5. JMM. Зачем нужно volatile. Популярный вопрос.SPLНе знаю как у вас, но у меня при упоминании JMM молниеносно всплывает в голове Алексей Шипилёв и его доклады — раз, два, три. Если вы больше чтец, чем смотрец, то Алексея можно и почитать — ать, два.
Кроме того, абсолютно не будет лишним посмотреть доклад Романа Елизарова по теоретическому минимуму JMM. Если совсем нет времени, то можно пробежаться по небольшой статейке по JMM. Если есть время и интерес, тогда углубляемся в тему через статью на Хабре. А ещё на Хабре есть неплохой перевод статьи "Многопоточность. Java-модель памяти": часть 1 и часть 2. Несомненным источником истины является Java Language Specification, раздел 17.4. Также ответ на этот вопрос можно прочитать на itsobes.ru. Не лишним будет ознакомиться с вопросом на JVM-уровне в статье How ‘volatile’ works on JVM level? на Medium. 6. Сборка мусора. Как работает? Какие сборщики знаете? Какие есть области памяти в JVM? Что будет с двумя или более объектами, которые ссылаюSPLПамять в Java делится на Stack и Heap.
Stack — это область памяти, доступ к которой организован в порядке LIFO. Сюда помещается frame — локальные переменные и параметры вызываемого метода. Здесь можно сразу уточнить, что примитивы хранятся на стеке, а вот у объектов тут хранится только ссылка, а сами объекты в Heap. НО, благодаря Escape Analysis и скаляризации из Java 6, объекты, которые являются исключительно локальными и не возвращаются за пределы выполняемого метода, также сохраняются в стеке. Про Escape Analysis и скаляризацию есть доклад (видео или текст) Руслана Черемина, или ещё тут. Frame создаётся и кладётся на Stack при вызове метода. Frame уничтожается, когда завершается его вызов метода, как в случае нормального завершения, так и в результате выброса неперехваченного исключения. У каждого потока есть свой Stack и он имеет ограниченный размер. Подробности можно посмотреть в JVM Specification. Теперь про Heap и сборку мусора. Тут большинство просто хочет услышать то, что написано в одном из сообщений telegram-канала Senior's Blog. Процитирую основную часть здесь: Heap делится на два поколения:
Young разделен на три части: Eden, Survivor 0 и Survivor 1. В Eden создаются все новые объекты. Один из Survivor регионов всегда пустой. При полном заполнении региона Eden запускается малая сборка мусора, и все живые объекты из Eden и Survivor перемещаются в пустой Survivor, а Eden и использующийся Survivor полностью очищается. Это делается для уменьшения фрагментации памяти. Объекты, которые несколько раз перемещаются между Survivor, затем помещаются в Tenured. В случае, когда места для новых объектов не хватает уже в Tenured, в дело вступает полная сборка мусора, работающая с объектами из обоих поколений. При этом старшее поколение не делится на подрегионы по аналогии с младшим, а представляет собой один большой кусок памяти. Поэтому после удаления мертвых объектов из Tenured производится не перенос данных (переносить уже некуда), а их уплотнение, то есть размещение последовательно, без фрагментации. Такой механизм очистки называется Mark-Sweep-Compact по названию его шагов (пометить выжившие объекты, очистить память от мертвых объектов, уплотнить выжившие объекты). Бывают еще объекты-акселераты, размер которых настолько велик, что создавать их в Eden, а потом таскать за собой по Survivor’ам слишком накладно. В этом случае они размещаются сразу в Tenured. Младшее поколение занимает одну треть всей кучи, а старшее, соответственно, две трети. При этом каждый регион Survivor занимает одну десятую младшего поколения, то есть Eden занимает восемь десятых.
Если совсем кратко, то можно ознакомиться тут и вот тут. Почитать на Хабре подробнее про сборку мусора в Java можно в серии статей "Дюк, вынеси мусор!" от alygin — раз, два, три. Послушать про работу с памятью и сборщиках мусора можно в выпуске 74 подкаста Podlodka с Алексеем Шипилёвом в гостях. Обязательно загляните в полезные ссылки к выпуску. Ещё можно вспомнить про:
Дополнительно про gc и саму JVM (ох, бохатая и животрепещущая тема):
7. Что такое Executor и ExecutorService, Thread pool и зачем нужны?SPLСоздавать и убивать потоки — дорого. Давайте создадим N потоков (Thread pool) и будем их переиспользовать. А давайте. Вот тут описано развёрнуто.
Executor (void execute(Runnable command) — вот и весь интерфейс) и ExecutorService (уже покруче, может запускать Callable и не только) — грубо говоря, интерфейсы выполняторов параллельных задач. А реализуют их различные выполняторы на пулах потоков. Экземпляры готовых конкретных выполняторов можно получить с помощью класса Executors. Если смелый-умелый и зачем-то надо, то можно и самому реализовать, конечно. Также подробнее можно почитать: 8. Могут ли быть в Java утечки памяти и когда? Как обнаружить причину? Как снять heap-dump?SPLМогут. Профилировать. Снимать heap-dump, например с помощью jmap, загружать в memory profiler (например в VisualVM)
Подробнее:
9. Что внутри параллельных стримов? На каком пуле работают параллельные стримы и в чем его особенность?SPLПо умолчанию parallel stream использует ForkJoinPool.commonPool размером Runtime.getRuntime().availableProcessors() — 1. Common pool создаётся статически при первом обращении к ForkJoinPool и живёт до System::exit (игнорирует shutdown() или shutdownNow()). Когда некий поток отправляет задачу в common pool, то pool может использовать его же в качестве воркера. Common pool один на всё приложение. Можно запустить stream на отдельном ForkJoinPool — завернуть параллельный stream в Callable и передать на вход методу submit созданного ForkJoinPool. Этот трюк работает благодаря методу fork() из ForkJoinPool (тут подробности).
Сам по себе ForkJoinPool представляет реализацию ExecutorService, выполняющую ForkJoinTask (RecursiveAction и RecursiveTask). Данный pool создан для упрощения распараллеливания рекурсивных задач и утилизации породивших подзадачу потоков. ForkJoinPool использует подход work stealing — у каждого потока есть его локальная очередь задач, из хвоста которой другие потоки могут тырить себе задачи, если у них закончились свои. Украденная задача делится и заполняет очередь задач потока. Подробнее:
10. Какие бывают операции в стримах? Напишите стрим?SPLЕсть 2 вида операций в Java Stream:
Кроме того, будет полезно ознакомиться с содержимым пакета java.util.stream и доступными коллекторами из Collectors. Периодически просят написать какой-нибудь стрим, поэтому хорошо бы попрактиковаться. Можно на работе наесться, можно придумать задачи самому себе, можно поискать что-нибудь готовое:
Почитать подробнее про стримы лучше в Java Doc, но можно и в статьях:
Посмотреть:
11. Что можно положить и достать из List<? extends Number>, а что с List<? super Number>? Что такое ковариантность, контрвариантность, инвариантность?SPLТут речь пойдёт про PECS — Producer extends, Consumer super (Joshua Bloch, Effective Java). А также вариантность — перенос наследования исходных типов на производные от них типы (контейнеры, делегаты, обобщения).
Ковариантность (covariance) — перенос наследования исходных типов на производные от них типы в прямом порядке. Переменной типа List<? extends T> разрешено присвоить экземпляр списка, параметризованного T или его подклассом, но не родительским классом. В список типа List<? extends T> нельзя добавить никакой объект (можно только null) — нельзя гарантировать какого именно типа экземпляр списка будет присвоен переменной, поэтому нельзя гарантировать, что добавляемый объект разрешён в таком списке. Однако, из списка можно прочитать объект и он будет типа T и экземпляром либо T, либо одного из подклассов T. Соответственно, List<? extends Number> можно присвоить ArrayList<Number> или ArrayList<Integer>, но не ArrayList<Object>. Метод get возвращает Number, за которым может скрываться экземпляр Integer или другого наследника Number. Массивы также ковариантны. Переопределение методов, начиная с Java 5, ковариантно относительно типа результата и исключений. List<?> аналогичен List<? extends Object> со всеми вытекающими. Контрвариантность (contravariance) — перенос наследования исходных типов на производные от них типы в обратном порядке. Переменной типа List<? super T> разрешено присвоить экземпляр списка, параметризованного T или его родительским классом, но не его подклассом. В список типа List<? super T> можно добавить экземпляр T или его подкласса, но нельзя добавить экземпляр родительских для T классов. Из такого списка с гарантией можно прочитать только Object, за которым может скрываться неизвестно какой его подкласс. Соответственно, List<? super Number> можно присвоить либо ArrayList<Number>, либо ArrayList<Object>, но не список наследников Number(т.е. никаких ArrayList<Integer>). Можно добавить экземпляр Integer или Double (можно было бы Number, но он абстрактный), но нельзя — Object. Метод get возвращает Object — точнее сказать нельзя. Инвариантность — наследование исходных типов не переносится на производные. Переменной типа List<T> разрешено присвоить экземпляр списка, параметризованного только T. В список можно добавить экземпляр T или его подкласса. Список возвращает T, за которым может скрываться экземпляр его подкласса. Соответственно, List<Number> можно присвоить ArrayList<Number>, но не ArrayList<Integer> или ArrayList<Object>. Можно добавить экземпляр Integer или Double (можно было бы Number, но он абстрактный), но нельзя — Object. Метод get возвращает Number, за которым может скрываться экземпляр Integer или другого наследника Number. Подробнее:
12. Как работает ConcurrentHashMap?SPLConcurrentHashMap — это потокобезопасная мапа (карта, словарь, ассоциативный массив, но тут и далее просто "мапа"), у которой отсутствуют блокировки на всю мапу целиком.
Особенности реализации:
В результате имеем:
На Хабре есть несколько устаревшая статья — будьте внимательны и осторожны с java 8 произошли изменения. Класс Segment<K,V> максимально урезан и сохранён только для обратной совместимости при сериализации, где и используется. concurrencyLevel также оставлен лишь для обратной совместимости и теперь служит в конструкторе только для увеличения initialCapacity до количества предполагаемых потоков-потребителей мапы: if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads Есть более современная статья с примером реализации ConcurrentMap. Также можно почитать гайд по ConcurrentMap на Baeldung. 13. Что такое Xmx и Xms, Xss?SPLJVM стартует с Xms количеством выделенной под heap памяти и максимально может увеличить её до значения Xmx.
Xss флаг определяет размер выделенной под стек памяти. Общий вид: java -Xmx<количество><единица измерения>
Можно использовать различные единицы измерения, например килобайты (k), мегабайты (m) или гигабайты (g). Пример: java -jar my.jar -Xms256m -Xmx2048m
Подробнее: 14. Как работают Атомики?SPLАтомарная операция — это операция, которая выполняется полностью или не выполняется совсем, частичное выполнение невозможно.
Атомики — это классы, которые выполняют операции изменения своего значения атомарно, т.о. они поддерживают lock-free thread-safe использование переменных. Достигается это с помощью алгоритма compare-and-swap (CAS) и работает быстрее, чем аналогичные реализации с блокировками. На уровне инструкций большинства процессоров имеется поддержка CAS. В общем случае работу Атомиков можно описать следующим образом. Атомик хранит некоторое volatile значение value, для изменения которого используется метод compareAndSet(current, new), поэтому предварительно читается текущее значение — current. Данный метод с помощью CAS изменяет значение value только в том случае, если оно равно ожидаемому значению (т.е. current), прочитанному перед запуском compareAndSet(current, new). Если значение value было изменено в другом потоке, то оно не будет равно ожидаемому. Следовательно, метод compareAndSet вернет значение false. Поэтому следует повторять попытки чтения текущего значения и запуска с ним метода compareAndSet(current, new) пока current не будет равен value. Условно можно разделить методы Атомиков на:
Непосредственно изменение значения value делегируется либо VarHandle, либо Unsafe, которые в свою очередь выполняют его на нативном уровне. VarHandle — это динамически сильно типизированная ссылка на переменную или на параметрически определяемое семейство переменных, включающее статические поля, нестатические поля, элементы массива или компоненты структуры данных нестандартного типа. Доступ к таким переменным поддерживается в различных режимах, включая простой доступ на чтение/запись, volotile доступ на чтение/запись и доступ на compare-and-swap. В java.util.concurrent.atomic имеется следующий набор атомиков:
С помощью атомиков можно реализовать блокировку, например так: public class NonReentrantSpinLock {
private AtomicReference<Thread> owner = new AtomicReference<>(); public void lock() { Thread currentThread = Thread.currentThread(); while (!owner.compareAndSet(null, currentThread)) {} } public void unlock() { Thread currentThread = Thread.currentThread(); owner.compareAndSet(currentThread, null); } } Подробнее:
15. Что внутри и как работают TreeSet/TreeMap? В чем идея Красно-черного дерева?SPLTreeMap — реализация NavigableMap, основанная на красно-чёрном дереве. Элементы отсортированы по ключам в натуральном порядке или с помощью Comparator, указанного при создании мапы, в зависимости от использовавшегося конструктора. Гарантирует логарифмическое время выполнения методов containsKey, get, put и remove.
TreeSet — реализация NavigableSet, основанная на TreeMap. Элементы отсортированы в натуральном порядке или с помощью Comparator, указанного при создании множества, в зависимости от использовавшегося конструктора. Гарантирует логарифмическое время выполнения методов add, contains и remove. Обе коллекции НЕ synchronized и итератор по ним может выбросить ConcurrentModificationException. Если в эти коллекции при использовании натурального порядка сортировки в качестве ключа попытаться положить null, то получим NullPointerException. В случае с компаратором поведение с null будет зависеть от реализации компаратора. До 7-й Java с добавлением null в TreeMap и TreeSet был баг. Самая важная особенность красно-чёрного дерева в том, что оно умеет само себя балансировать, поэтому не важно в каком порядке будут добавляться в него элементы, преимущества этой структуры данных будут сохраняться. Сбалансированность достигается за счёт поддержания правил красно-чёрной раскраски вершин:
Подробнее:
16. Что поменялось с Java 8 по Java <CURRENT_VERSION>?SPLJava имеет богатую историю. На данный момент проекты чаще всего разделяются на:
Между 8 и 9 версиями случился небольшой разлом с частичной потерей обратной совместимости, а потом приколы лицензирования подъехали, поэтому миграция и в без того консервативном мире Java-приложений идёт медленно. Однако идёт, и если вы собеседуетесь в компанию, где этот переход уже осуществили, то, вероятно, у вас поинтересуются, что же там с Java 8 поменялось, чем живёт и дышит современная Java. На момент выхода статьи, имеем:
Найти ссылки на документацию к API, языку и виртуальной машине, release notes и сравнить API между версиями можно в Java-альманахе. Кроме всего прочего, есть ряд проектов, в рамках которых развиваются большие и ожидаемые сообществом изменения Java:
Отдельно нужно упомянуть GraalVM — это JDK и виртуальная машина Java, которая создана, чтобы объединить необъединяемое:
Послушать на тему:
Почитать на Хабре:
Посмотреть:
17. В какой кодировке строки в Java? Как хранятся строки внутри класса String? Как устроен String?SPLДо Java 9 все строки имели кодировку UTF-16 (2 байта на символ) и хранились в массиве char.
С Java 9 пришло такое изменение как Compact String. Если все символы строки входят в множество символов Latin-1 (а это подавляющее большинство строк), то каждый из них может поместиться в 1 байт, поэтому в этом случае массив char избыточен. В результате было принято решение заменить массив char на массив byte, что позволяет строкам Latin-1 расходовать меньше памяти. Кодировка строки хранится в отдельном поле byte coder, значение которого представляет Latin-1 или UTF-16. Также интересной особенностью является кеширование классом String своего hashcode. Строки являются неизменяемыми, наследоваться от строк запрещено (final class). Все операции по изменении строки возвращают её новый экземпляр, в том числе и конкатенация строк. Компилятор умеет оптимизировать конкатенацию и превращать её в объект StringBuilder и совокупность вызовов методов append. ОДНАКО! В Java 9 вошёл JEP 280: Indify String Concatenation, который изменил эту оптимизацию и пошёл ещё дальше. Теперь вместо StringBuilder генерируется bytecode для вызова StringConcatFactory через invokedynamic, поэтому стоит расслабиться и чаще выбирать +. Ещё можно упомянуть про String pool — это выделяемое в heap пространство, которое используется для оптимизации потребления памяти при хранении строк. Благодаря ему одинаковые строковые литералы могут ссылаться на один и тот же объект. Стоит помнить, что с помощью [String.intern()](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/String.html#intern()) производительности особой не добиться, а можно наоборот пустить всё по миру. Лучше напишите свою реализацию. Подоробнее читайте в статье Алексея Шипилёва — JVM Anatomy Quark #10: String.intern(). Кроме того, equals и методы поиска (например indexOf) оптимизируются JIT компилятором на нативном уровне. Посмотреть доклады Алексея Шипилёва на тему строк: Катехизис java.lang.String и The Lord of the Strings: Two Scours. Подробнее:
18. Что такое ThreadLocal переменные?SPLThreadLocal — класс в виде обёртки для хранения отдельной независимой копии значения переменной для каждого использующего её потока, что позволяет сделать работу с такой переменной потокобезопасной.
Данные ThreadLocal-переменных хранятся не в них самих, а непосредственно в объектах Thread. У каждого экземпляра класса Thread есть поле ThreadLocal.ThreadLocalMap threadLocals, которое инициализируется и используется ThreadLocal. ThreadLocal.ThreadLocalMap представляет собой специализированную версию HashMap, записи которой наследуют от WeakReference<ThreadLocal<?>>, используя ключ мапы как ref field слабой ссылки. Ключами такой мапы являются ThreadLocal, а значением — Object. Если ключ записи равен null, то такая запись называется просроченной (stale) и будет удалена из мапы. Следует обратить внимание, что ThreadLocal изолирует именно ссылки на объекты, а не копии их значений. Если изолированные внутри потоков ссылки ведут на один и тот же объект, то возможны коллизии. Когда у ThreadLocal-переменной запрашивается её значение (например через метод get), то она получает текущий поток, извлекает из него мапу threadLocals, и получает значение из мапы, используя себя в качестве ключа. Аналогично выполняются методы изменения значения ThreadLocal. Из этого следует, что значение ThreadLocal-переменной должно устанавливаться в том же потоке, в котором оно будет использоваться. Подробнее: 19. Сколько в байт занимает каждый из примитивных типов в памяти? А объект?SPLКазалось бы:
А размер boolean не упоминается в спецификации вовсе. Однако также спецификация не запрещает использовать для хранения примитива больше памяти — главное, чтобы размер был достаточным для всех значений. Конкретный объём таки зависит от реализации JVM. Не последнюю роль в этом играет выравнивание данных в памяти. Похожая ситуация и со ссылочными типами — спецификация JVM не требует какой-то определённой структуры для объектов и отдаёт её на откуп реализации. Все тонкости и секреты занимаемой объектами памяти раскрывает Алексей Шипилёв в своей статье Java Objects Inside Out. Подробнее:
Если вас заинтересовало представление объектов в jvm и их реализация (и вы умеете-могёте читать C++), то можно пойти посмотреть исходники openjdk. Начать, например, отсюда: 20. Какие ссылки бывают в Java?SPLТипы ссылок в Java:
Чтобы достать объект из слабых ссылок, необходимо вызывать метод get(). Если объект недостижим, то метод вернёт null. Для фантомных ссылок всегда возвращается null. При создании слабой ссылки в конструктор можно, а для PhantomReference необходимо, передать экземпляр ReferenceQueue — в очереди будет сообщение, когда ссылка протухнет. Для SoftReference и WeakReference это будет ДО финализации объекта, а для PhantomReference ПОСЛЕ. Однако фактическое удаление объекта фантомной ссылки из памяти не производится до момента её очистки. Подробнее: Spring 21. Какие есть scope в Spring? Какой по умолчанию? Чем singleton отличается от prototype? Можно ли сделать свой scope и как? Плавно переходит в вопрос 'Как заиSPLSpring scope:
Про scope подробнее можно прочитать в документации, Bealdung. И, конечно же, надо посмотреть Spring-потрошитель Ч. 2. Про prototype в singleton можно вспомнить несколько вариантов:
22. Часто спрашивают о циклических зависимостях бинов в Spring. Проблема ли это или что получим в результате? Если проблема, то как её решить?SPLДа, это проблема — будет выброшено исключение BeanCurrentlyInCreationException (при внедрении зависимостей через конструктор).
Варианты решения:
Подробнее есть в документации и в Bealdung 23. Бывают вопросы про жизненный цикл бина, этапы инициализации контекста, про устройство спринга внутри, про DI и как он работаетSPLТут однозначно надо смотреть Spring-потрошитель часть 1 и часть 2. Также благое дело — это почитать документацию.
Также по этапам инициализации контекста есть статья с красивыми картинками на хабре. 24. Расскажите про прокси и про @Transactional. Как работает и зачем? Какие могут быть проблемы? Можно ли навесить @Transactional на приватный метод? А есSPLДля начала, если вы не знали или случайно забыли про паттерн Proxy в общем виде, то можно освежиться здесь.
Допустим, что наш сервис MyServiceImpl имеет 2 публичных метода, аннотированных @Transactional — method1 и method2(он с Propagation.REQUIRES_NEW). В method1 вызываем method2. В связи с тем, что для поддержки транзакций через аннотации используется Spring AOP, в момент вызова method1() на самом деле вызывается метод прокси объекта. Создается новая транзакция и далее происходит вызов method1() класса MyServiceImpl. А когда из method1() вызовем method2(), обращения к прокси нет, вызывается уже сразу метод нашего класса и, соответственно, никаких новых транзакций создаваться не будет.
Что тут можно ещё посоветовать? Spring-потрошитель опять и снова — часть 1 и часть 2. А также документация является несомненным и любимым первоисточником информации о Proxy и управление транзакциями. 25. Где у обычного (НЕ Boot) Spring-приложения main-класс?SPLСтарое доброе обычное Spring-приложение деплоится в контейнер сервлетов (или сервер приложений), где и расположен main-класс. При этом оно собирается в war-архив. Когда war-файл разворачивается в контейнере, контейнер обычно распаковывает его для доступа к файлам, а затем запускает приложение. Spring Boot приложение также можно собрать как war и задеплоить его таким же образом.
Подробнее:
26. Как работает Spring Boot и его стартеры?SPLВо-первых, благодаря spring-boot-starter-parent, у которого родителем является spring-boot-dependencies, можно особо не париться о зависимостях и их версиях — большинство версии того, что может потребоваться прописано и согласовано в dependencyManagement родительского pom. Или можно заимпортировать BOM.
Spring Boot черпает свою мощь из стартеров — наборов сконфигурированных бинов со всеми необходимыми зависимостями, готовых к использованию и доступных для тонкой настройки через properties-файлы. Для Spring Boot приложения создаётся main-класс с аннотацией @SpringBootApplication и запуском метода run класса SpringApplication, который возвращает ApplicationContext. @SpringBootApplication
public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } } Аннотация @SpringBootApplication просто скрывает за собой аннотации @EnableAutoConfiguration, @ComponentScan и @Configuration. SpringBootApplication создаёт либо WebApplicationContext (если в classpath есть Servlet и ConfigurableWebApplicationContext), либо GenericApplicationContext. При создании стартера используется файл META-INF/spring.factories — в нём ключу org.springframework.boot.autoconfigure.EnableAutoConfiguration приравнивается список из полных имён всех классов-конфигураций (а в них бины, @ComponentScan, @Import и т.п.) стартера через запятую. Аннотация @EnableAutoConfiguration импортирует AutoConfigurationImportSelector, который и отвечает за поиск необходимых классов конфигурации. Вызов метода getCandidateConfigurations обращается к SpringFactoriesLoader и его методу loadFactoryNames, чтобы просканировать classpath на наличие файлов META-INF/spring.factories и имен классов-конфигураций в них, а затем загрузить их в контекст. Также у Spring boot есть модуль spring-boot-autoconfigure со своим файлом META-INF/spring.factories. Чтобы не создавать все-все бины из конфигураций, у бинов используется аннотация @Conditional (или её вариации) с каким-либо условием. Чтобы упаковать Spring boot в jar используется spring-boot-maven-plugin. У такого jar в META-INF/MANIFEST.MF будет прописан Main-Class — org.springframework.boot.loader.JarLauncher, а в Start-Class будет уже main-класс нашего приложения. JarLauncher формирует class path (в начале в нём только org.springframework.boot), который находится в BOOT-INF(там lib с зависимостями и class с классами приложения), а затем запускает Start-Class. Посмотреть:
Почитать:
27. Как выполняется http-запрос в Spring?SPLВ современном Spring есть два подхода к построению веб-приложений:
Работа Spring MVC строится вокруг DispatcherServlet, который является обычным Servlet'ом и реализует паттерн Front Controller: принимает Http-запросы и координирует их с требуемыми обработчиками. Для своей конфигурации DispatcherServlet использует WebApplicationContext. DispatcherServlet в обработке запроса помогают несколько "специальных бинов" следующим образом:
Когда HTTP запрос приходит с указанным заголовком Accept, Spring MVC перебирает доступные HttpMessageConverter до тех пор, пока не найдет того, кто сможет конвертировать из типов POJO доменной модели в указанный тип заголовка Accept. HttpMessageConverter работает в обоих направлениях: тела входящих запросов конвертируются в Java объекты, а Java объекты конвертируются в тела HTTP ответов. По умолчанию, Spring Boot определяет довольно обширный набор реализаций HttpMessageConverter, подходящие для использования широкого круга задач, но также можно добавить поддержку и для других форматов в виде собственной или сторонней реализации HttpMessageConverter или переопределить существующие. Также стоит упомянуть, что как и в случае любого другого сервлета, к обработке запроса в Spring MVC может быть применена одна из реализаций интерфейса javax.servlet.Filter как до выполнения запроса, так и после. Sring MVC предоставляет несколько уже готовых реализаций. Отдельного разговора заслуживает путь запроса по внутренностям Spring Security, где используется множество различных фильтров. На хабре есть статья об этом. Подробнее:
Документация: Spring WebFlux — это реактивный веб-фреймворк, который появился в Spring Framework 5.0. Он не требует Servlet API (но может использовать Servlet 3.1+containers, хотя чаще это Netty (по умолчанию в Spring Boot) или Undertow), полностью асинхронный и неблокирующий, реализует спецификацию Reactive Streams при помощи проекта Reactor. В Spring WebFlux используется большинство аннотаций из Spring MVC (RestController, RequestMapping и другие) для определения аннотированных контроллеров. Однако представляет новую возможность создания функциональных котроллеров, основанных на HandlerFunction. В Spring WebFlux обработка запроса на стороне сервера строится в два уровня:
Контракт HttpHandler представляет обработку HTTP-запроса, как его прохождение через цепочку множества WebExceptionHandler, множества WebFilter и одного единственного WebHandler. Сборкой цепочки занимается WebHttpHandlerBuilder при помощи ApplicationContext. Диспетчеризация запросов в Spring WebFlux выполняется DispatcherHandler, который является имплементацией интерфейса WebHandler и также реализует паттерн Front Controller: принимает Http-запросы и координирует их с требуемыми обработчиками. DispatcherHandler — это Spring bean, имплементирующий ApplicationContextAware для доступа к контексту, с которым он был запущен. DispatcherHandler с бин-именем webHandler обнаруживает WebHttpHandlerBuilder и помещает в цепочку в качестве WebHandler. DispatcherHandler в ходе обработки http-запроса и ответа делегирует часть работы "специальным бинам", которые могут быть подвержены кастомизации, расширению и замене пользователем. Сам процесс обработки выглядит следующим образом: @Override
public Mono<Void> handle(ServerWebExchange exchange) { if (this.handlerMappings == null) { return createNotFoundError(); } return Flux.fromIterable(this.handlerMappings) .concatMap(mapping -> mapping.getHandler(exchange)) .next() .switchIfEmpty(createNotFoundError()) .flatMap(handler -> invokeHandler(exchange, handler)) .flatMap(result -> handleResult(exchange, result)); }
Документация: Продолжение следует Во второй части поговорим о Hibernate, базах данных, паттернах и практиках разработки, об одной популярной библиотеке, поддержке и сопровождении наших приложений, а также посмотрим на альтернативные шпаргалки и подведём итоги. =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 14:01
Часовой пояс: UTC + 5