Новий GC Epsilon. У джави може не бути сміття. Шок. Сенсація

Добрий день, панове!
Поспішаю повідомити, що настають останні дні.
Здається, світ Java розвинувся до такого ступеня, що ми тепер можемо спокійно використовувати Rust замість Java, то Java замість Rust.
Криваві подробиці чекають вас під катом.

Відомий інтернет-активіст Олексій Шипилев нещодавно затвитил наступне:

«Why don't we implement no-GC in HotSpot, and win every single 5-second latency benchmark. 100 GB heap is enough to survive for 10 secs.»

Круто, відмінна жарт — подумали ми і пішли їсти.
А повернувшись з кухні, подавилися відразу всіма плюшками, тому що там з'явилися скріншоти!





Ну гаразд, ми ж всі вміємо фотошопити, вірно?

А потім з'явився код, і справа почала приймати крутий оборот.
Ви розумієте, коли Шипилев щось викладає в паблік, то через місяць про це вже належить питати на співбесіді.

Тремтячими пальцями ми відкриваємо джаванет, і бачимо…
JEP: Epsilon GC: The Arbitrarily Low Overhead Garbage (Non-)Collector

Далі коротенько перекажу, що там написано. Мені було лінь робити точний переклад, граматичні воїни можуть пройти і прочитати оригінал :-)

Про б-же, що це, Беррімор?

Фіча полягає в тому, що GC дбає тільки про алокації нової пам'яті, а на розумні стратегії сміття можна забити. Як тільки доступна купа закінчується, починається процедура зупинки JVM.

Цілі

Надати повністю пасивну реалізацію GC з обмеженим обсягом пам'яті, яку можна виділить. В замін отримуємо найнижчий оверхед на продуктивність такого GC. Реалізація не повинна зачіпати інші GC або сильно щось міняти в JVM.

Мотивація

На правах автора топіка для хабра, трохи поспекулирую. Дійсно, адже не кожна компанія Google. Комусь хочеться просто зробити мінімальний працюючий продукт, і тільки потім дбати про його розвиток. Можливо, в майбутньому все доведеться переписати на більш просунутій платформі. Можливо, у розробників раніше закінчаться гроші. Для цього не потрібно тягнути в проект відразу величезні ынтерпрайзные штуки типу G1.

Окей, жарти в сторону, йдемо далі по тексту! Передбачається, що існує як мінімум 4 кейса, де така штука як Епсилон може реально стати в нагоді.

По-перше, з Epsilon можна порівнювати який-небудь інший більш просунутий GC, що допоможе в його розробці і вылавливании багів, привнесених самим механізмом складання.

По-друге, для істинно байтоебских (це термін, а не лайка!) додатків на Java, такий GC просто незамінний. Уявімо, що ви пишете прошивку для чергового чайника або розумного унітазу, і сам факт складання сміття вважається багом в додатку: замість того, щоб збирати сміття краще впасти, а розумні балансировщики розбалансують навантаження з інших нодам/VM. До чого повинні готувати унітаз, використовуючи мережу збалансованих VM, замнем для ясності. Крім того, використання Epsilon дозволить звільнитися від зайвих бар'єрів — родзинка на торті ідеального перфомансу.

По-третє, для тестування самого OpenJDK непогано мати засіб обмеження виділюваної пам'яті, щоб тестувати інваріанти навантаження на цю саму пам'ять. Зараз такі дані беруться з MXBeans або навіть парсятся по логам GC. Якщо GC буде підтримувати тільки обмежена кількість аллокаций, це реально спростить тестування розробникам OpenJDK. (Скоріше за все, читачі цієї статті — не розробники OpenJDK, але тепер можна спробувати зійти за разраба OpenJDK на черговій співбесіді.)

По-четверте, це допоможе встановити абсолютний мінімум для інтерфейсу VM-GC, і може служити доказом коректності його роботи. Що корисно, наприклад, JDK-8163329 («GC interface») (За посиланням стіна тексту, бажаючі можуть перевести на Хабр)

Подробиці

Для користувача, Epsilon виглядає як будь-який інший GC для OpenJDK, підключений за допомогою -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC.

Epsilon лінійно виділяє пам'ять в одному-єдиному послідовному шматку пам'яті. Це дозволяє використовувати найпростіший код для lock-free TLAB (thread-local allocation buffers), який може переиспользовать вже існуючий код з VM. Видача TLABов допомагає підтримувати резидентну пам'ять процесу в кількості, яка була реально виділено. Оскільки при такому розкладі і виділення великих шматків пам'яті, і TLABов, не сильно відрізняється, вони обробляються одним і тим же кодом.

Сет бар'єрів, що використовуються Epsilon, повністю порожній, оскільки Epsilon не робить ніяких справжніх циклів збірки, і отже йому абсолютно наплювати на граф об'єктів, помітки об'єктів, копіювання об'єктів, і весь непотрібний мотлох. Тільки хардкор, тільки перфоманс!

Так як єдино важлива частина рантайм інтерфейсу — та, де Epsilon видає TLABы, його перфоманс в основному залежить від розміру TLABов. Маючи довільно великі TLABы і довільно велику купу, оверхед продуктивності — завгодно мале додатне число, звідси і береться назва Epsilon. Автори та користувачі golang можуть починати зеленіти від заздрості і рвати волосся на дженериках.

Як тільки хіп весь закінчився, видати TLAB вже не можна, відновитися ніяк не можна, залишається тільки здатися і роздрукувати звіт про помилку. Тут можна зробити щось схоже на те, що роблять інші GC:

  • Кинути OutOfMemoryError з важким описом
  • Сдампить купу (як завжди, включається через -XX:+HeapDumpOnOutOfMemoryError
  • Жорстко впустити JVM, і опціонально виконати якесь зовнішнє дію (зазвичай-XX:OnError=...), наприклад запустити відладчик, оповістити систему моніторингу, або хоча б написати листа в Спортлото


Прототип був перевірений на невеликих навантаженнях, і планово падав на підвищених навантаженнях.
Код все ще лежить тут, поки його не забрав НЛО: http://cr.openjdk.java.net/~shade/epsilon/

Альтернативи

Альтернатив немає. Принаймні таких, щоб вирубали всі бар'єри. Серйозно.

Сподіваюся, що наша радість і ейфорія від зустрічі з дійсно унікальним GC, в чомусь схожа на ейфорію від тих препаратів, які вживає автор Epsilon, створюючи подібну годноту.

У будь-якому випадку, якщо бар'єри — не проблема, то Serial або Parallel (old) GC можуть дати схожий профіль продуктивності — якщо ви, звичайно, зможете налаштувати так, щоб збірка сміття ніколи не запускалася (наприклад, виставляючи величезні значення young gen, вимикаючи адаптивну евристику, ітп). Враховуючи, як багато там опцій, важко гарантувати, що це вийде.

Подальші поліпшення в сучасних GC, таких як Shenandoah, можуть призвести до зниження оверхеда до рівня, зневажливо малого у порівнянні з повністю no-op GC. Якщо/коду це станеться, Епсилон все ще можна буде використовувати для внутрішнього функціонального/навантажувального тестування (якщо на пенсії ви все ще будете хотіти займатися внутрішнім тестуванням GC).

А це взагалі працює? А це взагалі легально?

Звичайні тести нафіг не потрібні, і не годяться для Епсилона. Більшість з них вважають, що можна розкидати довільну кількість сміття. Тому для тестування Епсилона доведеться робити нові тести — треба перевірити що він працює на завданнях з низьким виділенням пам'яті, а при вичерпанні купи падає не аби як, а передбачувано. Для перевірки коректності вистачить нових jtreg тестів, що лежать в hotspot/gc/epsilon.

Одноразової перевірки, здійсненої під час розробки Epsilon, цілком достатньо, щоб припустити характеристики продуктивності на інтерпретаторі, C1 і C2. Подальше тестування нафіг не потрібно, так як поточна реалізація з самого початку розробки була стабільніша і железобетонней сталінських бункерів.

Ризики та припущення

Корисність супроти вартості підтримки. Можна припустити, що ця реалізація не варта свічок, бо все одно нікому не потрібна. З іншого боку, виходячи з досвіду, багато гравців джава екосистеми експериментували з викиданням GC з власних кастомних JVM. Значить, якщо у нас буде готовий no-op GC, це допоможе даної частини спільноти. Ну або принаймні дозволить пишатися, що ми використовували no-op GC, коли це ще не було мэйстримом. Враховуючи загальну дешевизну реалізації, ризики мінімальні.

Публічні очікування. Враховуючи, що цей мусоросборщик насправді не збирає ніякої сміття, хтось міг би сприйняти це як небезпечну практику. Включи ненароком Епсилон на продакшені, і відразу ж після переповнення купи, діячів чекають найнеприємніші известия. Автор припускає, що реально ніякого ризику немає, поки фіча проходить під прапором experimental, тобто вимагає включення -XX:+UnlockExperimentalVMOptions.

Складність реалізації. Можна уявити ситуацію, коли у спільному з іншими підсистемами коді доведеться поміняти більше, ніж спочатку передбачалося, наприклад, в компіляторі, або в бэкендах конкретних платформ. З іншого боку, по прототипу видно, що всі такі зміни з самого початку були жорстко ізольовані. Якщо це виявиться реальним ризиком, то може допомогти JDK-8163329 («GC interface»), про який вже згадувалося вище.

Залежності.

В цілях зменшення кількості змін у зовнішньому коді, ця робота залежить від JDK-8163329 («GC Interface»). Якщо зміни в загальному коді мінімальні, то воно не повинне вимагати змін в інтерфейсі GC.

Висновок

Чесно, я дуже подзадолбался все це писати.
Тому просто залишу це тут.
Хай буде з вами сила!
Вона вам знадобиться в новому світлому майбутньому, з Java без сміття.

P. S.: у Шипилева є посилання на цей пост, і він стежить за тобою, юзернейм
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.