Skip to content
Gallery
Theory for java developer
Share
Explore

+-------------+
| JDK |
| +---------+ |
| | JRE | |
| | +---+ | |
| | |JVM| | |
| | +---+ | |
| +---------+ |
+-------------+

Write once, run anywhere

JDK (Java Development Kit) - набор программ для разработки. JRE, загрузчик кода java, компилятор javac, архиватор jar, генератор документации javadoc и другие утилиты, для разработки.
JRE (Java Runtime Environment) - окружение, необходимое для запуска Java-программ. Включает в себя стандартную библиотеку. В нее входят, базовые пакеты lang, util... и пакеты для работы с различными форматами, базами данных, пользовательским интерфейсом.
JVM (Java Virtual Machine) - Виртуальная машина отвечает за само выполнение кода. Она работает с bytecode (тем, что находится внутри файлов с расширением .class).

JVM

Memory model java (depends on JVM version)

image.png
image.png
Native Memory - вся доступная системная память. ​Heap (куча) - объекты и статические переменные. Содержит все объекты приложения.
Переменные объекта - в heap с объектом.
Статические переменные - в heap с классом.
Eden, S0, S1, Old Generation - Подробнее в главе .
Thread Stack - локальные переменные и методы что вызвал поток (стек).
локальная переменная из методов в объекте – в stack, сам объект в heap.
локальная переменная ссылка на объект – в stack, сам объект в heap.
локальные переменные примитивных типов – в stack и не видны другим потокам.
Поток не может совместно использовать примитивную локальную переменную, только передать копию. Если два потока выполняют один и тот же код, они создадут свои копии в стеках. Все данные в стеке GC roots.
Metaspace (Permanent generation в Java 8) - метаданные классов . Это пространство также является общими для всех. Так как metaspace является частью native memory, то его размер зависит от платформы.
Code cache - JIT-компилятор компилирует часто исполняемый код, преобразует его в нативный машинный код и кэширует для более быстрого выполнения.

Компиляция

AOT-компиляция (ahead-of-time, статическая) – процесс превращения текста на языке программирования в нативный код на машинном языке. Так работают языки вроде C++.
JIT-интерпретация (just-in-time, динамическая) – «умная» интерпретация. Среда выполнения анализирует исполняемый код, оптимизируя часто вызываемые участки. Таким способом программа работает значительно быстрее. Именно с JIT-компиляцией связана необходимость «прогрева» программ перед тестированием производительности.
Execution Engen - преобразует bytecode в машинный.
Интерпретатор - Он считывает байт-код, интерпретирует (конвертирует) в машинный код и выполняет их последовательно. Проблема с интерпретатором заключается в том, что он интерпретирует каждый раз, даже один и тот же метод, что снижает производительность системы. Для преодоления этой проблемы в версии 1.1 появились JIT-компиляторы.

Class

Class loaders

System
class loader --> classpath
|
v
Platform
class loader --> jre/lib/ext
|
v
Bootstrap
class loader --> rt.jar
В JVM встроено как минимум три стандартных загрузчика:
Bootstrap – Написан на C++. Это базовый загрузчик, который загружает все системные классы из архива rt.jar (java.lang.* в частности) при старте.
Platform(Extension java 9) – загружает классы расширений из папки jre/lib/ext, загружаются по мере их использования.
System (Application) – загружает классы из classpath конкретного приложения, загружаются по мере их использования.
Перед тем как загрузить класс, ClassLoader проверит, не может ли это сделать его родитель. Если класс уже загружен, то загрузка не потребуется.
Право загрузки класса рекурсивно делегируется от самого нижнего загрузчика в иерархии к самому верхнему.

Загрузка класса

Сначала загружается класс и цепочка его предков. Он загружается один раз.
Выполняется выделение памяти и инициализация статических полей и блоки инициализации в порядке объявления.
Инстанцируется сам экземпляр и цепочки наследования, с самого дальнего родителя.
Выделяется память в куче для экземпляра, получается ссылка на этот экземпляр.
Выполняются выделение памяти и инициализация нестатических полей и блоков инициализации в порядке объявления.
Вызывается конструктор.

Типы классов

Абстрактный - помеченный ключевым словом abstract. Не может иметь экземпляры, может иметь абстрактные методы(с модификатором abstract) и обычные.
Финальный - с модификатором final, нерасширяемый. Модификаторы abstract и final несовместимы, но по отдельности применимы к различным внутренним классам (кроме анонимного).
Nested classes - Java позволяет создавать одни классы внутри других. Внутренние и вложенные классы могут иметь несколько уровней вложенности.
Внутренний (non-static nested / inner) - объявленный внутри другого класса. Не может иметь статических объявлений. Имеет доступ ко всем внутренностям экземпляра внешнего класса. Если член внешнего класса foo перекрыт членом внутреннего (shadowing), обратиться к внешнему можно с помощью конструкции OuterClassname.this.foo, без перекрытия сработает просто foo. Инстанциируется только от экземпляра внешнего класса: outer.new Inner().
Локальный - объявленный внутри метода. Является внутренним классом, в случае объявления в статическом методе без доступа к экземпляру внешнего класса. Не имеет модификаторов доступа.
Анонимный - локальный класс, объявленный без имени, непосредственно при инстанцирование, расширением другого класса или интерфейса. Анонимный может расширять только один класс или интерфейс. Не может быть абстрактным или финальным. Замена Лямбда-выражение.
Вложенные (static nested) - имеет доступ ко всем статическим членам внешнего класса. В остальном ничем не отличается от обычного класса;

Class Object / 11 methods

Object - superclass/parent всех классов в java которые неявно наследуются от него.
public final native Class<?> getClass() - Возвращает класс этого экземпляра. То есть результатом вызова .getClass() переменной типа Foo может быть как Foo.class, так и .class любого из его подклассов. Компилятор страхуется от ClassCastException в рантайме подменой возвращаемого типа метода на Class<? extends Foo>.
public native int hashCode(), public boolean equals(Object obj)
HashCode - для эффективного поиска. Default - адрес объекта (зависит от JVM).
Equals - для разрешения коллизий. Default - Сравнивает ссылки как ==.
Эти два метода придуманы для использования в Collections. Методы нужно переопределить чтобы эффективно использовать экземпляры как ключи в HashMap или HashSet. Они работают эффективнее, при хорошем распределение хэшей.
Контракты:
Нужно переопределять только вместе.
Если объекты равны у них одинаковый hashCode, но если hashCode одинаковый объекты не всегда равны ​(одинаковые поля для хэш кода или предельное значение int).
Переопределение:
@Ovveride
public boolean equals(Object o){
if (this == o) return true; //reflect
if (o == null) return false; // null check
// class type check and class casting
if (getClass() != o.getClass()) return false;
RestConfig that = (RestConfig) o;
// field check
return connectTimeOut == that.connectTimeOut
&& readTimeOut == this.readTimeOut
&& Objects.equals(url, that.url);
}
Рефлексивность - a.equals(a)
Null - Ничто не может быть equals(null).
Симметрия - a.equals(b), то b.equals(a)
Транзитивность - a.equals(b) и a.equals(c), то b.equals(c)
Согласованность - equals и hashCode должны возвращать одни и те же значения для одного и того же объекта при каждом последующем вызове, даже если состояние объекта изменилось. Это делает реализацию для изменяемых (mutable) объектов крайне сложной.

protected native Object clone() throws CloneNotSupportedException– реализации нет, вызов выбросит исключение. Нужно писать свою реализацию, делать при этом ее public и с маркерным интерфейсом Cloneable.
@Ovveride
protected Config clone() throws CloneNotSupportedException {
// we need to copy every reference with
// new instance if object is muable
// also each internal link
Config copied = (Config) super.clone();
copied.serUrl(new CustomObject("copied"));
}
По умолчанию «поверхностное копирование» - в объектах будут ссылки на одни и те же сущности поэтому нужно делать “Глубокие копии” где изменяемые данные с вложенной структурой тоже копируются, а не только их ссылки.
Это диктуется соглашением, по которому клон не должен зависеть от оригинала. По контракту клон должен быть другим объектом (!= оригиналу) и не зависеть от оригинала.
Альтернативы (многие считают что более удобные) метода clone - конструктор копирования и паттерн factory method.
public String toString() - getClass().getName() + '@' + Integer.toHexString(hashCode())
public final native void notify()  - возобновляет поток, из которого был вызван метод wait() для того же объекта
public final native void notifyAll() - возобновляет все потоков, из которых был вызван метод wait() для того же объекта. Управление передается одному из этих потоков.
public final void wait() throws InterruptedException - переводит вызывающий поток в состояние ожидания. В этом случае вызывающий поток освобождает монитор, который имеет доступ к ресурсу. Ожидание продолжается до тех пор, пока другой поток, который вошел в монитор, не вызовет метод.
protected void finalize() - deprecated работал при сборке мусора, ненадежен.

Abstract class vs Interface

Abstract Class
Описывает состояние и поведение
Средство наследования реализации
Строит иерархию наследования с отношением IS A(близкая связь)
extends один раз
Interface
Описывает только поведение
Средство наследования API
Устанавливает контракт (классы, у которых вообще нет ничего общего)
implements много
extends несколько раз нельзя из за diamond problem когда у разных parents одинаковая сигнатура методов и не понятно какой использовать в child при вызове.
Абстрактные метод: Без реализации, в абстрактном классе, со словом abstract. Методы интерфейса неявно абстрактные.
default метод в interface: До выхода Java 8, если добавлять в interface новые методы, это приводило к ошибкам компиляции в классах имплементирующих метод, чтобы не изменять каждый отдельный класс были придуманы методы с реализацией. Если имплементировать 2 Interface с одинаковой сигнатурой default методов - ошибка компиляции diamond problem.

Функциональные интерфейсы

Нужны для использования с lambda выражениями.
Это интерфейс, который содержит ровно один абстрактный метод, то есть описание метода без тела. Статические методы и default методы при этом не в счёт. 
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> con = (from) -> Integer.valueOf(from);
Integer converted = con.convert("123");
log.info(converted); // 123
| Functional interface | Descriptor |
|----------------------|----------------|
| Predicate<T> | T->boolean |
| BiPredicate<T,U> | (T,U)->boolean |
| Consumer<T> | T->void |
| BiConsumer<T, U> | (T,U)->void |
| Supplier<T> | ()->T |
| Function<T,R> | T->R |
| BiFunction<T,U,R> | (T,U)->R |
| UnaryOperator<T> | T->T |
| BinaryOperator<T> | (T,T)->T |

Reflection

Это средства манипуляции данными на основе знания о структуре классов этих данных. Использование Reflection API медленное и небезопасное. Позволяет ломать инвариантность состояний экземпляра, нарушать инкапсуляцию, и менять финальные поля.
Используют рефлексию обычно в тестах, в инструментах разработки, в фреймворках (особенно в связке с runtime-аннотациями).
Инициализация классов-конфигураций в Spring Framework. Фреймворк с помощью рефлекшна сканирует внутренности классов. Поля и методы, помеченные специальными аннотациями, воспринимаются как элементы фреймворка. AOP.

Garbage Collectors

Сборщик мусора отслеживает доступность каждого созданного объекта в heap и удаляет не используемые. Сборщиков мусора много, но почти все работают по схеме:
Mark - когда сборщик мусора находит какие куски памяти используются, а какие нет.
Sweep - на этом шаге удаляются объекты которые были помечены раньше.
Compact - собрать оставшиеся объекты в одну чанку.
При сборке мусора перемещаются между eden, s1, s2, old – только хедеры объектов.
-XX:+UseSerialGC - вызвать конкретный сборщик
-XX:Min(Max)HeapFreeRatio=? задают долю свободного места в каждом поколении.
Если на объект никто не ссылается и не в GC Roots:
Class: Classes loaded by a system class loader; contains references to static variables as well
Stack Local: Local variables and parameters to methods stored on the local stack
Active Java Threads: All active Java threads
Objects used as monitors for synchronization

Типы сборщиков мусора

Serial GC - самый старый, однопоточный.
Структура памяти: Eden, Survivor 0, Survivor 1, Tenured(Old) Плюсы: Использует мало ресурсов, нет оверхедов и побочных эффектов. ​Минусы: Долгие паузы на сборку мусора при заметных объемах данных. ​Когда использовать: RAM около 100Мб, не важны короткие остановки, 1 ядро процессора.
Parallel GC - параллельный, STW паузы короче, сам определяет количество потоков по ресурсам и параметрам. Можно указать параметры производительности — максимальное время сборки, пропускную способность — и сборщик будет стараться не превышать их.
-XX:MaxGCPauseMillis=400 - максимальная пауза ​-XX:ParallelGCThreads=? - количество тредов
Структура памяти: Eden, Survivor 0, Survivor 1, OldGen Плюсы: авто подстройки под требуемые параметры производительности и меньшие паузы. ​Минусы: фрагментация памяти. ​Когда использовать: простой и эффективный, подходит для большинства приложений. Нет скрытых накладных расходов.
Shenandoah GC - OpenJDK начиная с 12-й верси -XX:+UseShenandoahGC.

Типы данных, передача в метод, ключевые слова

| Type | byte (8 bits) | java size | range from -2(n-1) to +2(n-1)-1 |
| ------- | ------------- | --------- | ------------------------------------------- |
| byte | 1 | 4 | (-2 in 7) to (2 in 7 - 1) = -128 to 127 |
| short | 2 | 4 | (-2 in 15) to (2 in 15 - 1) |
| int | 4 | 4 | (-2 in 31) to (2 in 31 - 1) |
| long | 8 | 8 | (-2 in 63) to (2 in 63 - 1) |
| float | 4 | 4 | not important |
| double | 8 | 8 | not important |
| boolean | 1 bit | 4 | true to false |
| char | 2 | 2 | 0 to (2 in 16 - 1) |
8 примитивных и 8 Wrapper + String (9 ссылочных)
Float.valueOf(1) != Float.valueOf(1);
Integer.valueOf(1) == Integer.valureOf(1);
Long.valueOf(1L) == Long.valueOf(1L);
Integer.valueOf(1) != new Integer(1);
Integer.valueOf(999) != 999;
Integer.valueOf(1) == Integer.parseInt("1");
Character.valueOf('\u00') == (Character) ((char) 127);
Для всех классов-оберток над примитивами и String кроме Float и Double работает механизм кэширования.
Кэш реализован в виде pool-a значений для каждого типа, все pool-ы хранятся в heap.
Метод intern() - берет или кладет в pool.
Значения кэшируются и в других встроенных классах: BigDecimal, Currency, пустые коллекции. Кэши реализованы в коде классов.
Значения создаются на этапе инициализации класса, и пере используются когда объект создается не через new(например valueOf).
Передача в методы -
Java передает параметры по значению — скопировать значение и передать копию. В метод передается копия ссылки. Через которую можно изменять объект.

Сколько памяти занимает объект

Зависит от вендора JVM(32 или 64) и архитектуры CPU, говорим только про 64. Любой объект кратен 8 bytes. Минимальный размер когда есть только заголовок - 12 head + 4 padding = 16 bytes. Alignment/padding gap - чтобы адрес был кратным машинному слову, для ускорения чтения из памяти, уменьшения количества бит для указателя на объект, для уменьшения фрагментации памяти.
new String() { // 12 bytes head
private final char value[]; // 4 bytes ref
private final int offset; // 4 bytes
private final int count; // 4 bytes
private int hash; // 4 bytes
} // 24 all
// plus
new char[1] // 12 bytes head
// 4 bytes length
// 2 bytes * 1 char
// 6 bytes padding
// 24 all
new String("a"); // 48 at all
для просмотра памяти (Примитивы разные из за JVM)
# Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8
//ref,bool,byte,short,char,int,float,long,double
SimpleLong object internals:
OFFSET SIZE TYPE DESCRIPTION
0 12 (object header)
12 4 (alignment/padding gap)
16 8 long SimpleLong.state
Instance size: 24 bytes
Space losses: 4 internal + 0 external = 4 bytes total
Группировка полей
1. 8-ми байтовые типы(double и long)
2. 4-х байтовые типы(int и float)
3. 2-х байтовые типы(short и char)
4. Одно байтовые типы(boolean и byte)
5. Ссылочные переменные

Модификаторы доступа и ключевые слова

Порядок: @Аннотации, доступ, static final transient volatile В методах: @Аннотации, доступ, abstract static final synchronized native
| Scope | Class | Package | Child | All |
|-------------|-------|---------|--------|-----|
| private | + | - | - | - |
| default (-) | + | + | - | - |
| protected | + | + | + | - |
| public | + | + | + | + |

native - реализация метода скрыта внутри JVM, нельзя использовать в своем коде. ​transient - поле будет пропущено при сериализации.
abstract - synchronized и volatile -
final
class - запрет наследования
method - запрет переопределения
field с ссылкой - ссылка будет не изменяемой, поля самого объекта можно менять.
field - запрет изменения после инициализации
super
Задать нижнюю границу generic-типа: ​Consumer<? super Number>
Обратиться к члену класса-родителя, который перекрыт (shadowed) членами наследника или локальными переменными: ​int foo = super.foo
Вызвать в конструкторе конструктор родителя: ​SubClass() { super("subclass param"); }
default - В Java 8 вместе с лямбдами и стримами появилась необходимость дополнить новыми методами интерфейсы. Чтобы не ломать обратную совместимость, добавили методы с телом через слово default. Новые методы в старых интерфейсах.
static - Ключевое слово static используется для объявления вложенных классов, статических методов, полей, блоков инициализации и статических импортов.
Статические поля и методы – члены класса, а не экземпляра(instance), потому к ним можно обращаться через имя класса. Код статического блока или метода имеет доступ только к статическим членам. Статические поля не участвуют в сериализации. Инициализаторы статических полей выполняются в неявном статическом блоке.
Статические методы - используют раннее связывание, то есть вызов конкретного метода разрешается на этапе компиляции, не работают перегрузка и переопределение в наследниках.
Статический блок инициализации - выполняется потокобезопасно, один раз после загрузки класса загрузчиком. Блоков может быть несколько, выполнятся они в порядке объявления.
Static import - импортирует статические члены классов в .java-файл.

Аннотации

Retention Policy / @Retention – где останется аннотация после компиляции.
SOURCE – аннотация присутствует только в исходном коде. Два типа:
Маркеры - вариант документации. Примеры: @Immutable и @ThreadSafe из Hibernate.
Инструкции - для инструментов разработки @SuppressWarnings и @Override могут влиять на предупреждения и ошибки компиляции.
CLASS – попадает в байткод .class-файла, но игнорируется загрузчиком классов. Недоступна для рефлекшна. Используется для инструментов, обрабатывающих байткод.
RUNTIME – для снабжения метаинформацией, доступной во время выполнения программы. Используется для чтения метаинформации класса/объекта через Reflection API. Используется во множестве популярных фреймворков: Spring, Hibernate, Jackson.
Мета-аннотации – это аннотации для объявления других аннотаций. Вообще мета-аннотациями можно назвать любую аннотацию с таргетом ANNOTATION_TYPE.
@Repeatable – применяема несколько раз к одному и тому же элементу.
@Deprecated – устаревший.
@Inherited – применяется к наследникам. @Target - определяет, в каком контексте может применяться объявляемая аннотация.
Возможные таргеты:
TYPE – Объявление класса, интерфейса, аннотации или enum-а. FIELD – Объявление поля (включая константы enum-ов). METHOD – Объявление метода. PARAMETER – Формальный параметр в объявлении метода. CONSTRUCTOR – Объявление конструктора. LOCAL_VARIABLE – Объявление локальной переменной. ANNOTATION_TYPE – Объявление аннотации. Применяется для создания мета-аннотации. PACKAGE – Объявление пакета (в package-info.java). TYPE_PARAMETER – Объявление generic типа-параметра. TYPE_USE – Любое использование типа. Например: (@NonNull String) myObject. MODULE – Объявление модуля.

Виды String

Классы String, StringBuffer и StringBuilder. Класс String final для того чтобы работал string pool, не изменяемости паролей и прочего, потокобезопасности, имена классов. А эти два вспомогательных класса реализуют для него паттерн Builder и служат способом редактирования строки без относительно дорогого пересоздания объекта.
Все методы StringBuffer синхронны. В Java 1.5 ему на замену пришел несинхронизированный вариант StringBuilder. Похоже на HashMap и Hashtable. В остальном эти два класса почти ничем не отличаются, имеют одинаковый набор методов и конструкторов.
return "Hello" + "World"; // Compiled into
return "HelloWorld";
return "Hello" + x; // Compiled into
return new StringBuilder.append("Hello").append(x).toString();
Для буфера и билдера не работает синтаксический сахар строк:
Их нельзя создать литералом, вместо этого используется обычный конструктор. Нельзя конкатенировать +, вместо этого используются обычные методы insert и append.
Сам оператор конкатенации константных выражений, компилируется в интернированную строку, но для не-констант неявно использует StringBuilder String a = “cat”; String b = “ca” + “t”; a == b → true

Exception

Иерархия исключений:
Throwable, RunTime, Error, Exception - classes. ​Error - ошибки jvm, исполняемой среды. (Нельзя обработать и восстановиться) ​Exception - ошибки которые мы можем обработать, ошибка в коде. (Можно восстановиться)
Хорошей практикой считается использовать не проверяемые исключения. Т.к. проверяемые раскрывают инкапсуляцию(были введены давно и устарели), по комментарию к методу можно легко понять что нужно ловить. Во многих языках применяются только не проверяемые исключения. Разница между в checked и unchecked что мы обязаны обработать checked или пробросить дальше.
Блок finally может вернуть значение и shadowing исключение, как будто его и не было.
Interface Closable закрывает ресурс при вызове close(). Autocloseable – закрывает автоматически при try with resources.
Диаграмма без названия.drawio.png

Stream API

Это средства потоковой обработки данных в функциональном стиле. Для работы с набором данных как с одной сущностью, можно представить в виде конвейера.
Операции:
Source(Источником) - может быть заранее заданный набор данных, или динамический генератор, возможно даже бесконечный.
Intermediate(Промежуточные) - операции модифицируют стрим. Можно вызвать сколько угодно таких операций. Возвращает Stream.
Terminal(Терминальная) - операция выполняет всю цепочку и возвращает значение. Может быть только одна, в конце stream. Стрим не выполнится до вызова терминальной операции.
// Source
Stream.of(myArray) // Создание из массива
myCollection.stream() // Создание из коллекции
Stream.iterate(0, i -> i * i) // Бесконечный стрим квадратов
Stream.generate(() -> new Random().nextInt()) // Случайных чисел
Stream.empty() // Пустой стрим
Stream.of(x) // Из одного элемента
Stream.concat(a, b) // Склеить два стрима
// Intermadiate
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.