Какие проблемы могут возникнуть при работе с адресным пространством и многопоточностью
Что проверяют
Понимаете ли вы, что потоки делят одно адресное пространство процесса, и что из этого следует для JVM: куча общая, стеки свои. Следом обычно спрашивают про модель памяти Java или про то, почему приложение падает с OutOfMemoryError, когда куча почти пустая.
Как отвечать
Начните с базы: у каждого процесса своё виртуальное адресное пространство, а потоки одного процесса видят одну и ту же память. В JVM общие куча и метаспейс, а у каждого потока свой стек (размер задаёт -Xss, на 64-битном Linux по умолчанию 1 МБ) и свой буфер выделения объектов TLAB. Отсюда два класса проблем.
Общая память:
- гонки:
count++из нескольких потоков теряет обновления, нуженAtomicInteger,LongAdderили блокировка; - видимость и переупорядочивание: без happens-before поток может не увидеть запись другого или увидеть её не в том порядке. Инструменты:
volatile,synchronized,final-поля и безопасная публикация; - false sharing: независимые переменные в одной кеш-линии мешают друг другу.
Цена потоков:
- нативная память: стеки платформенных потоков живут вне кучи. Тысячи потоков дают
OutOfMemoryError: unable to create native thread, а в контейнере процесс убивает OOM killer, хотя до-Xmxдалеко; ThreadLocalв пулах: поток пула живёт долго, и значение безremove()держит объекты, а на серверах приложений ещё и загрузчик классов;StackOverflowError: глубокая рекурсия упирается в стек конкретного потока.
С Java 21 есть виртуальные потоки: их стеки лежат в куче и растут по мере надобности, поэтому сотни тысяч потоков реальны. Но гонки и видимость остаются прежними, а ThreadLocal на таком числе потоков легко съедает память.
Где спотыкаются
Путают процесс и поток и говорят, что у каждого потока своя память. Свой у потока только стек, всё из кучи доступно всем.
Лечат гонку на count++ через volatile. Он даёт видимость, но не атомарность.
Считают, что память процесса равна -Xmx. Стеки, метаспейс, code cache и direct-буферы лежат сверху.