Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Один день из жизни JVM-инженера

Один день из жизни JVM-инженера

Наш JVM-мир состоит из двух слоев: на одном Java- и Kotlin-разработчики, крутые специалисты по Spring и Hibernate, мастера энтерпрайза и огромных серверных приложений. Но есть мнение, что внутри этого Земного шара имеется другой шар, значительно больше наружного! Речь про второй слой: разработку самой виртуальной машины Java, которой пользуются Java-разработчики.

В докладе заглянем в этот другой мир: как живут разработчики JVM? Какие задачи и вызовы перед ними стоят? Чем их жизнь отличается от классической Java-разработки? И может ли Java-разработчик перейти на другую сторону, став JVM-инженером?

Ivan Ugliansky

November 20, 2022
Tweet

More Decks by Ivan Ugliansky

Other Decks in Programming

Transcript

  1. Иван, здравствуйте! Сейчас мы ищем сильных java разработчиков в нашу

    команду. Позвольте обратиться к Вам с предложением рассмотреть вакансию Senior Java Developer открытую в нашей компании.
  2. Иван, здравствуйте! Сейчас мы ищем сильных java разработчиков в нашу

    команду. Позвольте обратиться к Вам с предложением рассмотреть вакансию Senior Java Developer открытую в нашей компании. Помоги, пожалуйста, с написанием тестового задания на Java? А как на Java сделать вот это?
  3. Простите, но я не Java разработчик. Я разработчик Java. Иван,

    здравствуйте! Сейчас мы ищем сильных java разработчиков в нашу команду. Позвольте обратиться к Вам с предложением рассмотреть вакансию Senior Java Developer открытую в нашей компании. Помоги, пожалуйста, с написанием тестового задания на Java? А как на Java сделать вот это?
  4. Что в докладе 10 1. Кто такие JVM-инженеры, и зачем

    они нужны? 2. Как именно разрабатываются фичи в JVM? 3. Байки из жизни
  5. Что в докладе 11 1. Кто такие JVM-инженеры, и зачем

    они нужны? 2. Как именно разрабатываются фичи в JVM? 3. Байки из жизни 4. Почему так долго все делаете??
  6. Что в докладе 12 1. Кто такие JVM-инженеры, и зачем

    они нужны? 2. Как именно разрабатываются фичи в JVM? 3. Байки из жизни 4. Почему так долго все делаете??
  7. Как работает Java? 13 public static void main(String[] args) {

    for (int i = 0; i < 10; i++) { Object obj = new Object(); System.out.println(obj.hashCode()); } } test.java
  8. Как работает Java? 14 public static void main(String[] args) {

    for (int i = 0; i < 10; i++) { Object obj = new Object(); System.out.println(obj.hashCode()); } } 0: iconst_0 1: istore_1 2: iload_1 3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return javac test.java test.class
  9. Как работает Java? 15 public static void main(String[] args) {

    for (int i = 0; i < 10; i++) { Object obj = new Object(); System.out.println(obj.hashCode()); } } 0: iconst_0 1: istore_1 2: iload_1 3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return javac test.java test.class frontend-разработка (никакого JavaScript-а!)
  10. Как работает Java? 16 0: iconst_0 1: istore_1 2: iload_1

    3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return test.class
  11. Как работает Java? 17 0: iconst_0 1: istore_1 2: iload_1

    3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return test.class JVM
  12. Как работает Java? 18 0: iconst_0 1: istore_1 2: iload_1

    3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return test.class JVM Счастье
  13. Как работает Java? 19 0: iconst_0 1: istore_1 2: iload_1

    3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return test.class JVM ... add rsp, byte -24 mov eax, dword [rsp-0C00H] lea rax, [rel L002] mov qword [rsp], rax lea rdi, [rsp+8H] xor eax, eax stosq stosq xor ebx, ebx jmp short L012 ; v add ebx, byte 1 lea rdi, [rsp+14H] xor eax, eax stosd lea rbp, [rel _0java_lang_Object] mov qword [rsp+8H], rbp mov dword [rsp+10H], -2126479360 lea rcx, [rsp+8H] mov rsi, qword [rel _4java_lang_System_out] call hashCode mov edx, eax mov rax, qword [rsi] mov rcx, rsi mov rax, qword [rax+1D0H] call rax ...
  14. Как работает Java? 20 0: iconst_0 1: istore_1 2: iload_1

    3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return test.class JVM Интерпретация
  15. Как работает Java? 21 0: iconst_0 1: istore_1 2: iload_1

    3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return test.class JVM Интерпретация JIT-компиляция
  16. Как работает Java? 22 0: iconst_0 1: istore_1 2: iload_1

    3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return test.class JVM Интерпретация JIT-компиляция AOT-компиляция
  17. Как работает Java? 23 0: iconst_0 1: istore_1 2: iload_1

    3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return test.class JVM Интерпретация JIT-компиляция AOT-компиляция И рантайм!
  18. Дела в компиляторе: 24 ◦ Глобально - породить машинный код:

    ✓ под нужную платформу ✓ корректный* ✓ эффективный
  19. Дела в компиляторе: 25 ◦ Глобально - породить машинный код:

    ✓ под нужную платформу ✓ корректный* ✓ эффективный ◦ Более конкретно: ✓ Parsing => Lowering => Code generation ✓ DCE, Inline, Loop unrolling, Global value numbering, Scalar replacement, Register allocation, Bounds-checking optimizations, and more and more
  20. Дела в рантайме: 28 ◦ Глобально — помочь полученному машинному

    коду работать (эффективно) ◦ Более конкретно: ✓ Аллокация памяти и сборка мусора ✓ Поддержка tiered compilation: от интерпретатора к более оптимизированному коду (и обратно!)
  21. Дела в рантайме: 29 ◦ Глобально — помочь полученному машинному

    коду работать (эффективно) ◦ Более конкретно: ✓ Аллокация памяти и сборка мусора ✓ Поддержка tiered compilation: от интерпретатора к более оптимизированному коду (и обратно!) ✓ Реализация высокоуровневых фич языков: исключения, синхронизация, threading, рефлексия, интероп и т.д.
  22. Неявные исключения 31 struct st { int a, b; };

    int main() { struct st * ptr = NULL; ptr->a = 42; }
  23. Неявные исключения 32 struct st { int a, b; };

    int main() { struct st * ptr = NULL; ptr->a = 42; } Segmentation fault (core dumped) точнее UB
  24. Неявные исключения 33 class Test { int a; } public

    static void main(String[] args) { Test t = null; t.a = 42; } Exception in thread "main" java.lang.NullPointerException: Cannot assign field "a" because "t" is null at Main.main(Main.java:9)
  25. Неявные исключения 34 class Test { int a; } public

    static void main(String[] args) { Test t = null; t.a = 42; } Как добиться такого поведения? Exception in thread "main" java.lang.NullPointerException: Cannot assign field "a" because "t" is null at Main.main(Main.java:9)
  26. Неявные исключения 35 public static void foo(Test t) { if

    (t != null) { t.a = 42; } else { throw new NullPointerException(); } } foo(null);
  27. Неявные исключения 36 public static void foo(Test t) { if

    (t != null) { t.a = 42; } else { throw new NullPointerException(); } } foo(null);
  28. Неявные исключения 37 public static void foo(Test t) { if

    (t != null) { // [COMPILER-GENERATED] t.a = 42; } else { // [COMPILER-GENERATED] throw new NullPointerException(); } } foo(null);
  29. Неявные исключения 38 public static void foo(Test t) { if

    (t != null) { // [COMPILER-GENERATED] t.a = 42; } else { // [COMPILER-GENERATED] throw new NullPointerException(); } } foo(null); Каждый доступ к полю, каждый вызов функции обрамлять проверкой? Но это же очень дорого!
  30. Неявные исключения 39 public static void foo(Test t) { t.a

    = 42; } foo(null); Каждый доступ к полю, каждый вызов функции обрамлять проверкой? Но это же очень дорого! А как на самом деле?
  31. Неявные исключения 40 public static void foo(Test t) { t.a

    = 42; } foo(null); -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly mov DWORD PTR [rsi+0xc], 0x2a ;*putfield a {reexecute=0 rethrow=0 return_oop=0} ; - Main::foo@34 (line 13) Каждый доступ к полю, каждый вызов функции обрамлять проверкой? Но это же очень дорого! А как на самом деле?
  32. Неявные исключения 41 public static void foo(Test t) { t.a

    = 42; } foo(null); -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly mov DWORD PTR [rsi+0xc], 0x2a ;*putfield a {reexecute=0 rethrow=0 return_oop=0} ; - Main::foo@34 (line 13) ; implicit exception: dispatches to 0x0000028cc Каждый доступ к полю, каждый вызов функции обрамлять проверкой? Но это же очень дорого! А как на самом деле?
  33. Неявные исключения 42 public static void foo(Test t) { t.a

    = 42; } foo(null); а пусть взрывается!
  34. Неявные исключения 43 public static void foo(Test t) { t.a

    = 42; } foo(null); SEGFAULT а пусть взрывается!
  35. Неявные исключения 44 public static void foo(Test t) { t.a

    = 42; } foo(null); SEGFAULT Segfault перехватывается рантаймом! а пусть взрывается!
  36. Неявные исключения 45 public static void foo(Test t) { t.a

    = 42; } foo(null); SEGFAULT В этой точке может быть NPE? throw new NullPointerException() да нет core dump Segfault перехватывается рантаймом! а пусть взрывается!
  37. Неявные исключения 47 ◦ Бесплатные, когда исключение не случается ◦

    Дорогие, когда случаются, но это редко! (а если будет часто случаться - деоптимизируем)
  38. Неявные исключения 48 ◦ Бесплатные, когда исключение не случается ◦

    Дорогие, когда случаются, но это редко! (а если будет часто случаться - деоптимизируем) ◦ Чтобы сработали, рантайм фильтрует сегфолты (когда весь мир рушится!), полезные превращает в исключения ◦ Похожим образом могут обрабатываться деление на ноль, SOE, safe-points и даже clinit checks
  39. new то ненастоящий! 50 ◦ Выделять объекты в памяти ⇒

    нагружать GC ◦ Поэтому JVM максимально старается этого избежать
  40. new то ненастоящий! 51 ◦ Выделять объекты в памяти ⇒

    нагружать GC ◦ Поэтому JVM максимально старается этого избежать ◦ Для этого попытаемся понять, пользуются ли объектом только локально, или же он утекает в кучу. Это называется анализом утеканий или Escape Analysis
  41. new то ненастоящий! 52 class Test { int a, b;

    } public void foo() { Test obj = new Test(); obj.a = 42; obj.b = 10; System.out.println(obj.a + obj.b); }
  42. new то ненастоящий! 53 class Test { int a, b;

    } public void foo() { Test obj = new Test(); obj.a = 42; obj.b = 10; System.out.println(obj.a + obj.b); } obj существует только в этой области. Ну зачем его аллоцировать в хипе?
  43. class Test { int a, b; } public void foo()

    { Test obj = new Test(); obj.a = 42; obj.b = 10; System.out.println(obj.a + obj.b); } new то ненастоящий! 54 В таком случае можно применить оптимизацию Scalar Replacement 1. Объект распадается на атомы (работаем с его полями, как с обычными локалами) 2. Заголовка нет, аллокации тем более 👍
  44. class Test { int a, b; } public void foo()

    { // REMOVED BY COMPILER // Test obj = new Test(); int a = 42; int b = 10; System.out.println(a + b); } new то ненастоящий! 55 В таком случае можно применить оптимизацию Scalar Replacement 1. Объект распадается на атомы (работаем с его полями, как с обычными локалами) 2. Заголовка нет, аллокации тем более 👍
  45. class Test { int a, b; } public void bar(Test

    arg) { arg.b = 10; System.out.println(arg.a + arg.b); } public void foo() { Test obj = new Test(); obj.a = 42; bar(obj); } new то ненастоящий! 56
  46. class Test { int a, b; } public void bar(Test

    arg) { arg.b = 10; System.out.println(arg.a + arg.b); } public void foo() { Test obj = new Test(); obj.a = 42; bar(obj); } new то ненастоящий! 57 Допустим bar не проинлайнился, тогда так просто не взорвешь! Становится дороже передавать внутренности объекта в функции, чем просто ссылку на этот объект.
  47. new то ненастоящий! 58 Допустим bar не проинлайнился, тогда так

    просто не взорвешь! Становится дороже передавать внутренности объекта в функции, чем просто ссылку на этот объект. Но при этом new все еще лишний, утеканий все еще нет. class Test { int a, b; } public void bar(Test arg) { arg.b = 10; System.out.println(arg.a + arg.b); } public void foo() { Test obj = new Test(); obj.a = 42; bar(obj); }
  48. new то ненастоящий! 59 Допустим bar не проинлайнился, тогда так

    просто не взорвешь! Становится дороже передавать внутренности объекта в функции, чем просто ссылку на этот объект. Но при этом new все еще лишний, утеканий все еще нет. Тогда можно заменить new на аллокацию на стеке. class Test { int a, b; } public void bar(Test arg) { arg.b = 10; System.out.println(arg.a + arg.b); } public void foo() { Test obj = new Test(); obj.a = 42; bar(obj); }
  49. new то ненастоящий! 60 class Test { int a, b;

    } public void bar(Test arg) { arg.b = 10; System.out.println(arg.a + arg.b); } public void foo() { Test obj = new [on stack] Test(); obj.a = 42; bar(obj); } obj.header obj.a obj.b arg foo bar
  50. 61 class Test { int a, b; } static Test

    t; public void foo(boolean shouldEscape) { Test obj = new Test(); obj.a = 42; obj.b = 10; System.out.println(obj.a + obj.b); if (shouldEscape) { t = obj; } }
  51. class Test { int a, b; } static Test t;

    public void foo(boolean shouldEscape) { Test obj = new Test(); obj.a = 42; obj.b = 10; System.out.println(obj.a + obj.b); if (shouldEscape) { t = obj; } } 62 С точки зрения любого анализа escape здесь случается (пусть и не всегда)
  52. class Test { int a, b; } static Test t;

    public void foo(boolean shouldEscape) { Test obj = new Test(); obj.a = 42; obj.b = 10; System.out.println(obj.a + obj.b); if (shouldEscape) { t = obj; } } 63 С точки зрения любого анализа escape здесь случается (пусть и не всегда) Но есть более мощный анализ: partial escape analysis, который позволяет сделать так
  53. class Test { int a, b; } static Test t;

    public void foo(boolean shouldEscape) { int a = 42; int b = 10; System.out.println(a + b); if (shouldEscape) { Test obj = new Test(); obj.a = a; obj.b = b; t = obj; } } 64 С точки зрения любого анализа escape здесь случается (пусть и не всегда) Но есть более мощный анализ: partial escape analysis, который позволяет сделать так
  54. class Test { int a, b; } static Test t;

    public void foo(boolean shouldEscape) { int a = 42; int b = 10; System.out.println(a + b); if (shouldEscape) { Test obj = new Test(); obj.a = a; obj.b = b; t = obj; } } 65 С точки зрения любого анализа escape здесь случается (пусть и не всегда) Но есть более мощный анализ: partial escape analysis, который позволяет сделать так Эвакуация объекта в heap только там, где это правда нужно. Зачастую это холодный путь. Реализовано в компиляторе Graal
  55. new то ненастоящий! 66 ◦ Многие new - тоже бесплатные,

    можно не боятся их ставить в своем коде, JVM постарается их убрать ◦ Scalar replacement - в HotSpot (и везде), Partial EA в Graal компиляторе ◦ Stack allocation - в IBM OpenJ9, в HotSpot есть только прототип и proposal: github.com/microsoft/openjdk-proposals/blob/main/stack_all ocation/Stack_Allocation_JEP.md
  56. 67 РАНТАЙ М КО М П И ЛЯ О Р

    Оптимизации производительности и потребления памяти
  57. Как разрабатывать JVM? 72 На чем писать рантаймы и компиляторы?

    C++? Но почему именно на нем? Производительность и связь с низким уровнем
  58. Как разрабатывать JVM? 73 На чем писать рантаймы и компиляторы?

    C++? Но почему именно на нем? Производительность и связь с низким уровнем Но все не так однозначно!
  59. На чем писать компилятор? 74 Задача компилятора - породить машинный

    код: ✓ под нужную платформу ✓ корректный ✓ эффективный
  60. На чем писать компилятор? 75 Задача компилятора - породить машинный

    код: ✓ под нужную платформу ✓ корректный ✓ эффективный Берем байты на вход (исходную программу), генерировать байты на выходе (машинный код)! Нет никакой связи с "низким уровнем"!
  61. Как разрабатывать JVM? 76 На чем писать рантаймы и компиляторы?

    C++? Но почему именно на нем? Производительность и связь с низким уровнем Но все не так однозначно!
  62. На чем писать компилятор? 77 Задача компилятора - породить машинный

    код: ✓ под нужную платформу ✓ корректный ✓ эффективный Насколько быстро это нужно сделать?
  63. На чем писать компилятор? 78 Задача компилятора - породить машинный

    код: ✓ под нужную платформу ✓ корректный ✓ эффективный Насколько быстро это нужно сделать? 1. Для AOT компиляторов - не так важно
  64. На чем писать компилятор? 79 Задача компилятора - породить машинный

    код: ✓ под нужную платформу ✓ корректный ✓ эффективный Насколько быстро это нужно сделать? 1. Для AOT компиляторов - не так важно 2. Для JIT компиляторов важнее, платим за производительность компилятора стартапом приложения, но…
  65. Как работает Java? 81 public static void main(String[] args) {

    for (int i = 0; i < 10; i++) { Object obj = new Object(); System.out.println(obj.hashCode()); } } 0: iconst_0 1: istore_1 2: iload_1 3: bipush 10 5: if_icmpge 32 8: new #2 11: dup 12: invokespecial #1 15: astore_2 16: getstatic #3 19: aload_2 20: invokevirtual #4 23: invokevirtual #5 26: iinc 1, 1 29: goto 2 32: return javac test.java test.class Получившийся байт-код должен быть: 1. Корректным 2. Универсальным frontend-разработка (никакого JavaScript-а!)
  66. На чем писать компилятор? 83 Компилятор Режим работы На чем

    написан javac AOT* Java HotSpot C1/C2 JIT C++ IBM OpenJ9 compiler JIT & AOT C++
  67. На чем писать компилятор? 84 Компилятор Режим работы На чем

    написан javac AOT* Java HotSpot C1/C2 JIT C++ IBM OpenJ9 compiler JIT & AOT C++ Graal JIT & AOT Java (!)
  68. На чем писать компилятор? 85 Компилятор Режим работы На чем

    написан javac AOT* Java HotSpot C1/C2 JIT C++ IBM OpenJ9 compiler JIT & AOT C++ Graal JIT & AOT Java (!) Excelsior JET compiler AOT/JIT Scala/Java (!!)
  69. На чем писать рантайм? 87 Производительность и связь с низким

    уровнем Вот в рантайме и то и другое нужно, как воздух
  70. На чем писать рантайм? 88 Производительность и связь с низким

    уровнем Вот в рантайме и то и другое нужно, как воздух А еще: нужна предсказуемость/непрерывность исполнения и полный контроль над ним
  71. На чем писать рантайм? 89 Производительность и связь с низким

    уровнем Вот в рантайме и то и другое нужно, как воздух А еще: нужна предсказуемость/непрерывность исполнения и полный контроль над ним Значит все-таки C++?
  72. На чем писать рантайм? 91 На самом деле - полноценный

    рантайм для Native Image На чем написан?
  73. 95 На чем писать рантайм? На чистой Java (или любом

    другом обычном managed языке) писать рантаймы не получится, но… …можно делать свои специализированные полу-managed языки, которые вам подойдут Пример: SubstrateVM/SystemJava
  74. Как разрабатывать JVM? 96 На чем писать рантаймы и компиляторы?

    Некоторые современные JVM разрабатываются не на C++, а на managed языках (Java, Scala, их вариации для рантайма)
  75. Как разрабатывать JVM? 97 На чем писать рантаймы и компиляторы?

    Некоторые современные JVM разрабатываются не на C++, а на managed языках (Java, Scala, их вариации для рантайма) Это значит: разработка в IDEA, юнит-тесты и более низкий порог входа в разработку
  76. Отлаживай так, словно никто не видит 100 Допустим, мы ошиблись

    при разработке GC Проявления: ◦ Собираются живые объекты ⇒
  77. Отлаживай так, словно никто не видит 101 Допустим, мы ошиблись

    при разработке GC Проявления: ◦ Собираются живые объекты ⇒ ✓ развалы JVM (не должно происходить никогда!)
  78. Отлаживай так, словно никто не видит 102 Допустим, мы ошиблись

    при разработке GC Проявления: ◦ Собираются живые объекты ⇒ ✓ развалы JVM (не должно происходить никогда!) ✓ некорректное поведение!
  79. Отлаживай так, словно никто не видит 103 Допустим, мы ошиблись

    при разработке GC Проявления: ◦ Собираются живые объекты ⇒ ✓ развалы JVM (не должно происходить никогда!) ✓ некорректное поведение! ◦ Не собираются мертвые объекты ⇒ ✓ утечки памяти (формально не баг)
  80. Отлаживай так, словно никто не видит 104 Допустим, мы ошиблись

    при разработке GC Ну, подумаешь! Отладим. Да, но…
  81. Отлаживай так, словно никто не видит 105 Допустим, мы ошиблись

    при разработке GC Ну, подумаешь! Отладим. Да, но… ◦ Проблемы проявляются раз в N запусков (где N может быть ~10000)
  82. Отлаживай так, словно никто не видит 106 Допустим, мы ошиблись

    при разработке GC Ну, подумаешь! Отладим. Да, но… ◦ Проблемы проявляются раз в N запусков (где N может быть ~10000) ◦ В отладчике не проявляется (тайминги не те)
  83. Отлаживай так, словно никто не видит 107 Допустим, мы ошиблись

    при разработке GC Ну, подумаешь! Отладим. Да, но… ◦ Проблемы проявляются раз в N запусков (где N может быть ~10000) ◦ В отладчике не проявляется (тайминги не те) ◦ Логи не помогут - диска не хватит
  84. Отлаживай так, словно никто не видит 108 Допустим, мы ошиблись

    при разработке GC Ну, подумаешь! Отладим. А как отлаживать то тогда???
  85. Отлаживай так, словно никто не видит 109 Допустим, мы ошиблись

    при разработке GC Ну, подумаешь! Отладим. А как отлаживать то тогда??? ◦ Ассерты по всему коду рантайма ◦ Ловушки и терпеливое ожидание ◦ Формальная верификация!
  86. Эффект бабочки 111 Случай из жизни: 1. У некоторых пользователей

    проявляется странный развал в недрах msvcr100.dll
  87. Эффект бабочки 112 Случай из жизни: 1. У некоторых пользователей

    проявляется странный развал в недрах msvcr100.dll 2. На нашей стороне, конечно, не воспроизводится
  88. Эффект бабочки 113 Случай из жизни: 1. У некоторых пользователей

    проявляется странный развал в недрах msvcr100.dll 2. На нашей стороне, конечно, не воспроизводится 3. Разваливается не только наша JVM! 4. У всех есть одно общее: Windows 10 Creators Update, а кроме того…
  89. Эффект бабочки 116 Разгадка: 1. У всех пользователей на рабочем

    столе была "Папка Бога" 2. В Windows 10 Creators Update изменилось поведение системного вызова IShellFolder::GetDisplayNameOf(...)
  90. Эффект бабочки 117 Разгадка: 1. У всех пользователей на рабочем

    столе была "Папка Бога" 2. В Windows 10 Creators Update изменилось поведение системного вызова IShellFolder::GetDisplayNameOf(...) 3. JVM использовала нативные методы на C, которые этот сискол дергают, не проверяя результат 4. Божественная папка вызывала случайные развалы
  91. Выводы про отладку 118 ◦ Баги в JVM дороги и

    коварны ◦ Отладка — мощный детективный challenge ◦ На поведение сильно влияют O/S и железо (memtest в помощь)
  92. Выводы про отладку 119 ◦ Баги в JVM дороги и

    коварны ◦ Отладка — мощный детективный challenge ◦ На поведение сильно влияют O/S и железо (memtest в помощь) ◦ Для системного программирования нужны языки, гарантирующие безопасность ◦ Парадоксальным образом managed языки иногда подходят лучше, чем C++
  93. Клиенты - это Java программисты В целом - это очень

    круто! Пока клиенты не начинают использовать sun.misc.Unsafe
  94. История из жизни 124 ERROR - com.esotericsoftware.kryo.KryoException: Unknown offset Serialization

    trace: ... at com.esotericsoftware.kryo.serializers.UnsafeCacheFields.getField at com.esotericsoftware.kryo.serializers.ObjectField.write at com.esotericsoftware.kryo.serializers.FieldSerializer.write at com.esotericsoftware.kryo.Kryo.writeClassAndObject
  95. История из жизни 125 ERROR - com.esotericsoftware.kryo.KryoException: Unknown offset Serialization

    trace: ... at com.esotericsoftware.kryo.serializers.UnsafeCacheFields.getField at com.esotericsoftware.kryo.serializers.ObjectField.write at com.esotericsoftware.kryo.serializers.FieldSerializer.write at com.esotericsoftware.kryo.Kryo.writeClassAndObject Проявляется (иногда) у пользователей Kryo На Hotspot не проявляется 😔
  96. История из жизни 126 offset = unsafe().objectFieldOffset(f); ... public Object

    getField(Object object) throws IllegalArgumentException, IllegalAccessException { if (offset >= 0) { return unsafe().getObject(object, offset); } else throw new KryoException("Unknown offset"); }
  97. offset = unsafe().objectFieldOffset(f); ... public Object getField(Object object) throws IllegalArgumentException,

    IllegalAccessException { if (offset >= 0) { return unsafe().getObject(object, offset); } else throw new KryoException("Unknown offset"); } История из жизни 127 звучит логично!
  98. 128 /** * Reports the location of a given field

    in the storage * allocation of its class. Do not expect to perform any * sort of arithmetic on this offset; it is just a cookie * which is passed to the unsafe heap memory accessors. * … * */ @ForceInline public long objectFieldOffset(Field f) { ... } sun.misc.Unsafe.java
  99. 129 /** * Reports the location of a given field

    in the storage * allocation of its class. Do not expect to perform any * sort of arithmetic on this offset; it is just a cookie * which is passed to the unsafe heap memory accessors. * … * */ @ForceInline public long objectFieldOffset(Field f) { ... } sun.misc.Unsafe.java
  100. История из жизни 132 Почему наши оффсеты были отрицательными? 0

    0 1 1 (long) offset == 12 Много свободного места в старших битах!
  101. История из жизни 133 Почему наши оффсеты были отрицательными? 0

    0 1 1 (long) offset == 12 Много свободного места в старших битах! Будем использовать в своих целях (например, чтобы быстро распознать интерфейсы) 0 1 1
  102. История из жизни 134 Почему наши оффсеты были отрицательными? 0

    0 1 1 (long) offset == -6917529027641081844 Много свободного места в старших битах! Будем использовать в своих целях (например, чтобы быстро распознать интерфейсы) 0 1 1
  103. public Object getField(Object object) throws IllegalArgumentException, IllegalAccessException { if (offset

    >= 0) { return unsafe().getObject(object, offset); } else throw new KryoException("Unknown offset"); } История из жизни 135 эта проверка проваливалась для офсетов интерфейсных полей!
  104. public Object getField(Object object) throws IllegalArgumentException, IllegalAccessException { if (offset

    != Unsafe.INVALID_FIELD_OFFSET) { return unsafe().getObject(object, offset); } else throw new KryoException("Unknown offset"); } История из жизни 136
  105. public Object getField(Object object) throws IllegalArgumentException, IllegalAccessException { if (offset

    != Unsafe.INVALID_FIELD_OFFSET) { return unsafe().getObject(object, offset); } else throw new KryoException("Unknown offset"); } История из жизни 137
  106. Практические выводы 138 ◦ Приватные API лучше не использовать! ◦

    Если очень нужно, то быть готовым к развалам, изменению логики и плохой работе с кастомными JVM ◦ Проверяйте в сорцах своей JVM
  107. А почему все так долго делают? 140 synchronized (obj) {

    ... } ◦ На любом Java объекте можно синхронизироваться
  108. А почему все так долго делают? 141 synchronized (obj) {

    ... } ◦ На любом Java объекте можно синхронизироваться ◦ Базовая реализация через нативные мониторы native monitor
  109. А почему все так долго делают? 142 synchronized (obj) {

    ... } ◦ На любом Java объекте можно синхронизироваться ◦ Базовая реализация через нативные мониторы ◦ Идея для оптимизации #1: обычно конкуренции за монитор нет Thin lock stack CAS-ы вместо нативных мониторов
  110. А почему все так долго делают? 143 synchronized (obj) {

    ... } ◦ На любом Java объекте можно синхронизироваться ◦ Базовая реализация через нативные мониторы ◦ Идея для оптимизации #1: обычно конкуренции за монитор нет ◦ Идея для оптимизации #2: зачастую вообще только один тред входит в synchronized block owner thread-id Один CAS на первом входе, дальше просто сравнение
  111. А почему все так долго делают? 144 ◦ Оптимизация называется

    Biased Locking ◦ Реализована во многих JVM, включая HotSpot до Java 19 ◦ Звучит так, что все круто и просто! ◦ Но есть нюанс..
  112. 145

  113. А почему все так долго делают? 146 ◦ Оптимизация называется

    Biased Locking ◦ Реализована во многих JVM, включая HotSpot до Java 19 ◦ Звучит так, что все круто и просто! ◦ Но есть нюанс: дикая сложность реализации в рантайме
  114. А почему все так долго делают? 147 ◦ Оптимизация называется

    Biased Locking ◦ Реализована во многих JVM, включая HotSpot до Java 19 ◦ Звучит так, что все круто и просто! ◦ Но есть нюанс: дикая сложность реализации в рантайме ◦ А еще ухудшение производительности на некоторых примерах (Cassandra)
  115. А почему все так долго делают? 148 ◦ Оптимизация называется

    Biased Locking ◦ Реализована во многих JVM, включая HotSpot до Java 19 ◦ DEPRECATED in JDK 15 ◦ REMOVED in JDK 19
  116. А почему все так долго делают? 149 ◦ Оптимизация называется

    Biased Locking ◦ Реализована во многих JVM, включая HotSpot до Java 19 ◦ DEPRECATED in JDK 15 ◦ REMOVED in JDK 19 Теперь все довольны? Так ведь?
  117. https://mail.openjdk.org/pipermail/hotspot-dev/2022-October/065496.html boolean contentChanged = false; BufferedInputStream oldContent = ...; BufferedInputStream

    newContent = ...; try { int newByte = newContent.read(); int oldByte = oldContent.read(); while (newByte != -1 && oldByte != -1 && newByte == oldByte) { newByte = newContent.read(); oldByte = oldContent.read(); } contentChanged = newByte != oldByte; } catch (IOException e) { contentChanged = true; } ... This code slows down considerably when biased locking is not available.(с)
  118. https://mail.openjdk.org/pipermail/hotspot-dev/2022-October/065496.html public synchronized int read() throws IOException { if (pos

    >= count) { fill(); if (pos >= count) return -1; } return getBufIfOpen()[pos++] & 0xff; } Последствия java.io.BufferedInputStream.java
  119. https://gist.github.com/shipilev/71fd881f7cbf3f87028bf87385e84889 Последствия private void readOut(Blackhole bh, InputStream s) throws IOException

    { int b; while ((b = s.read()) != -1) { bh.consume(b); } } @Benchmark public void fis(Blackhole bh) throws IOException { try (InputStream s = new new FileInputStream(file)) { readOut(bh, s); } }
  120. https://gist.github.com/shipilev/71fd881f7cbf3f87028bf87385e84889 Последствия private void readOut(Blackhole bh, InputStream s) throws IOException

    { int b; while ((b = s.read()) != -1) { bh.consume(b); } } @Benchmark public void fis_bis(Blackhole bh) throws IOException { try (InputStream s = new BufferedInputStream(new FileInputStream(file))) { readOut(bh, s); } }
  121. https://gist.github.com/shipilev/71fd881f7cbf3f87028bf87385e84889 Последствия # ---- JDK 17, +BiasedLocking Buffers.fis 1000 avgt

    10 319.211 ± 2.068 us/op Buffers.fis_bis 1000 avgt 10 5.855 ± 0.027 us/op # ---- JDK 19 (no biased locking support) Buffers.fis 1000 avgt 10 310.152 ± 2.300 us/op Buffers.fis_bis 1000 avgt 10 13.626 ± 0.032 us/op
  122. Последствия 156 ◦ Вилка - слишком сложный код в рантайме,

    который трудно поддерживать или клиенты, у которых теперь тормозит код ◦ Хорошего решения нет, но удаление biased locking выходит боком (жалобы уже есть) ◦ В других JVM biased locking остался
  123. Разработка JVM - это: 159 1. Challenge! Научный и инженерный

    2. Детектив 3. Общение с клиентами Java программистами
  124. Можно ли стать гномом JVM инженером? 162 Конечно! Стоит начать

    с чтения: shipilev.net/jvm/anatomy-quarks А потом сорцы: github.com/openjdk/jdk/tree/master/src/hotspot github.com/oracle/graal
  125. 163

  126. Безопасность 169 class Test { int a; } public static

    void main(String[] args) { Test t = null; System.out.println(t.a); } Никакого UB в мою смену!
  127. Безопасность 170 class Test { int a; } public static

    void main(String[] args) { Test t = null; System.out.println(t.a); } Никакого UB в мою смену! А как это работает??? Hardware Exceptions!
  128. IdentityHashCode 172 ◦ От каждого объекта в Java можно позвать

    hashCode(), переопределяли вы его или нет ◦ Если нет, виртуальная машина предоставит вам дефолтную реализацию: System.identityHashCode()
  129. IdentityHashCode 173 ◦ От каждого объекта в Java можно позвать

    hashCode(), переопределяли вы его или нет ◦ Если нет, виртуальная машина предоставит вам дефолтную реализацию: System.identityHashCode() ◦ Требования к хешу: ✓ идемпотентность (сколько раз не зовем, результат одинаковый) ✓ хорошее распределение
  130. IdentityHashCode 174 ◦ От каждого объекта в Java можно позвать

    hashCode(), переопределяли вы его или нет ◦ Если нет, виртуальная машина предоставит вам дефолтную реализацию: System.identityHashCode() ◦ Требования к хешу: ✓ идемпотентность (сколько раз не зовем, результат одинаковый) ✓ хорошее распределение Как такое реализовать?
  131. https://gist.github.com/shipilev/71fd881f7cbf3f87028bf87385e84889 Последствия # ---- JDK 17, +BiasedLocking Buffers.fis 1000 avgt

    10 319.211 ± 2.068 us/op Buffers.fis_bis 1000 avgt 10 5.855 ± 0.027 us/op # ---- JDK 17, -BiasedLocking Buffers.fis 1000 avgt 10 316.564 ± 2.085 us/op Buffers.fis_bis 1000 avgt 10 13.120 ± 0.059 us/op # ---- JDK 19 (no biased locking support) Buffers.fis 1000 avgt 10 310.152 ± 2.300 us/op Buffers.fis_bis 1000 avgt 10 13.626 ± 0.032 us/op