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

Нас Spring Boot, а мы крепчаем: невыносимая лег...

Нас Spring Boot, а мы крепчаем: невыносимая легкость AOT компиляции Spring приложений.

Spring Framework использует, пожалуй, весь набор динамических свойств Java: Spring Boot-приложения грузятся своим загрузчиком классов, повсеместно используются reflection, порождение и загрузка Java-байткода на лету и т. д.

С другой стороны, в мире микросервисов набирает популярность статическая AOT-компиляция Java-приложений для решения проблем быстрого старта, предсказуемой производительности, мгновенного достижения пиковой производительности. Становится интересно: может ли динамичность Spring Framework ужиться со статической компиляцией? Ответ — да, может.

В этой презентации на примере поддержки Spring Boot-приложений в AOT-центричной JVM Excelsior JET показывается, как динамические свойства Java, используемые в Spring Framework, могут работать совместно с AOT-порожденным машинным кодом.

Nikita Lipsky

April 05, 2019
Tweet

More Decks by Nikita Lipsky

Other Decks in Programming

Transcript

  1. Никита Липский Excelsior Нас Spring Boot, а мы крепчаем Невыносимая

    легкость AOT компиляции Spring приложений
  2. Никита Липский ? •  Инициатор проекта Excelsior JET –  работал

    над проектом более 18 лет –  как идейный вдохновитель –  компиляторный инженер –  руководитель –  и много в каких еще ролях •  twitter: @pjBooms •  team blog: https://www.excelsiorjet.com/blog 9
  3. Excelsior JET? •  Полная реализация Java SE –  c 2005

    года cертифицирована как Java Compatible •  AOT compiler + Java Runtime –  смешанная компиляция: AOT + JIT –  поддержка нестандартных загрузчиков классов в AOT режиме (для Eclipse RCP, Tomcat, Spring Boot) •  Toolkit –  Startup Optimizer –  Deployment 10
  4. Никита Липский ? •  Инициатор проекта Excelsior JET –  работал

    над проектом более 18 лет –  как идейный вдохновитель –  компиляторный инженер –  руководитель –  и много в каких еще ролях •  twitter: @pjBooms •  team blog: https://www.excelsiorjet.com/blog 11
  5. Секретная биография ? •  1.5 года руководства разработкой интернет сервиса

    –  Spring (MVC, Security, AOP, etc.) –  JPA (EclipseLink) –  MySQL –  Liquibase –  ELK –  Angular –  Amazon EC2, EBS 12
  6. План доклада •  Spring –  context, configuration, classpath scanning, bean

    definitions processing, DI •  Spring Boot –  auto configuration, @SpringBootApplication, Spring Boot executable archive •  Как Spring работает на уровне JVM –  reflection, dynamic proxies •  Как Spring AOT компилируется –  почему все продолжает работать •  Как AOT оптимизирует DI паттерн •  Бенчи на производительность, стартап 13
  7. 18

  8. 19

  9. 21

  10. 23

  11. Spring Application Context @Component class MyComponent @Service class MyService @Configuration

    class MyConfig { @Bean MyBean myBean(){ new MyBean(); } } Context 24
  12. @Autowired Object postProcess(Object bean, String beanName) { Field[] fields =

    bean.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Autowired.class)) { field.setAccessible(true); ReflectionUtils.setField(field, bean, cxt.getBean(field.getType()); } } return bean; } 35
  13. 36

  14. @Configuration @Configuration class MyConfig { @Bean MyBean1 myBean1() { new

    MyBean1(); } @Bean MyBean2 myBean2() { new MyBean2(myBean1()); } } 37
  15. Auto @Configuration @Configuration // Some conditions class MyAutoConfiguration { //

    Auto-configured beans @Configuration @ConditionalOnClass(EmbeddedAcmeService.class) static class EmbeddedConfiguration { @Bean @ConditionalOnMissingBean public EmbeddedAcmeService embeddedAcmeService() { ... } } } 38
  16. @SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan @SpringBootApplication public class

    Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 39
  17. Spring Boot Executable архив +---org | \---springframework | \---boot |

    \---loader | | JarLauncher.class | | LaunchedURLClassLoader.class | +---jar | | JarFile.class | \---BOOT-INF +---classes | \—example | Example.class | +---lib | spring-core.jar, spring-boot.jar, spring-web.jar 40
  18. Spring Boot Executable архив •  LaunchedURLClassLoader грузит классы из BOOT-INF/classes,

    BOOT-INF/lib/* •  Доступ к классам жаров из BOOT-INF/lib осуществляется через свою реализацию JarFile 42
  19. Spring Boot •  Все такое динамичное: –  Везде reflection – 

    Разбор аннотаций –  Генерация и загрузка proxy классов –  Рантайм связывание Как такой AOT? 43
  20. Spring Boot •  Все такое динамичное: –  Везде reflection – 

    Разбор аннотаций –  Генерация и загрузка proxy классов –  Рантайм связывание Как такой AOT? 44
  21. Reflection field.set(bean, cxt.getBean("beanName")); Parameter[] cparams = constr.getParameters(); //if cparams.length ==

    1 constr.newInstance( cxt.getBean(cparams[0].getName())); method.invoke(bean, cxt.getBean(mparams[0].getName())); 48
  22. Reflection //from java/lang/Class.java private native Field[] getDeclaredFields0(boolean publicOnly); private native

    Method[] getDeclaredMethods0(boolean publicOnly); private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly); 49
  23. JVM OS + CPU Monitoring AOT Class files Classloading engine

    Bytecode Verification Execution engine: interpreter, JIT Threading Synchronization Meta information Memory management, Garbage Collection Native methods 50
  24. JVM •  JVM при загрузке классов строит их рантайм представление

    и складывает результат в Metaspace •  Reflection реализован через прямой доступ к Metaspace 56
  25. JVM +AOT AOT Class files Classloading engine Reflection JNI Metaspace

    Class1 Class2 Class3 Class4 Class5 Class6 57
  26. JVM +AOT AOT Class files Classloading engine Reflection JNI Metaspace

    Class1 Class2 Class3 Class4 Class5 Class6 58
  27. JVM +AOT AOT Class files Classloading engine Reflection JNI Metaspace

    Class1 Class2 Class3 Class4 Class5 Class6 59
  28. JVM +AOT AOT Class files Classloading engine Reflection JNI Metaspace

    Class1 Class2 Class3 Class4 Class5 Class6 60
  29. JVM +AOT AOT Class files Classloading engine Reflection JNI Metaspace

    Class1 Class2 Class3 Class4 Class5 Class6 61
  30. JVM + AOT •  В JVM с AOT компилятором Reflection

    работает практически также как в обычной JVM за тем лишь исключением, что рантайм представление класса строится AOT компилятором до исполнения 62
  31. Java Runtime JDK classes JVM Metaspace C1 C2 C3 C4

    C5 C6 Reflection Executable Meta data Native code GC JIT Threading 78
  32. Java Runtime Java File System OS OS File System Executable

    Meta data Native code Embedded File System Jar1 Jar2 Jar3 90
  33. Classpath •  Jars внутри Exe формируют свою (виртуальную) файловую систему

    и монтируются к Java файловой системе вместе с OS файловой системой. 91
  34. Classpath •  URL из classpath ccылаются на эти виртуальные файлы.

    •  getResource() загрузчика через java.util.zip находит ресурсы внутри jars зашитых в executable. 92
  35. Classpath Но в jars лежат не только ресурсы, но и

    классы. Они тоже попадают в Exe? 94
  36. Synthetic Class Files Проблема: спрингу нужны классы приложения во время

    исполнения. Решение: порождать их из метаинформации 96
  37. Synthetic Class Files Проблема: спрингу нужны классы приложения во время

    исполнения. Решение: порождать их из метаинформации 97
  38. AOT + JIT Spring порождает классы: •  Spring AOP – Порождение

    Proxy класса, который перед выполнением кода проксируемого класса исполняет код аспекта (пример @Transactional) 107
  39. AOT + JIT Spring порождает классы: •  Для @Configuration классов

    – порождение наследника переопределяющего @Bean методы 108
  40. AOT + JIT Вопрос: как загружаются классы порожденные Spring в

    случае AOT? Ответ: c помощью JIT компилятора (линковка с AOT кодом через metaspace) 109
  41. Executable Meta data Native code Resources Java Runtime JDK classes

    OS+CPU DynProxy Classes getProxy() JVM 112
  42. Executable Meta data Native code Resources Java Runtime JDK classes

    JVM OS+CPU DynProxy JITed code Classes JIT getProxy() 113
  43. AOT + JIT Вопрос: как загружаются классы порожденные Spring в

    случае AOT? Ответ: c помощью JIT компилятора (линковка с AOT кодом через metaspace) 114
  44. AOT + JIT Вопрос: как загружаются классы порожденные Spring в

    случае AOT? Ответ: c помощью JIT компилятора (линковка с AOT кодом через metaspace) 115
  45. Нестандартные загрузчики классов •  Переопределяют умолчательную логику разрешений ссылок между

    классами •  Уникальное пространство имен •  Позволяют управлять зависимостями •  В случае Spring Boot позволяют прятать зависимости внутрь Spring Boot executable jar 117
  46. Нестандартные загрузчики классов Как компилировать статически? •  Компилировать каждый класс

    изолировано от других –  Плохо для производительности •  Изучить логику разрешения ссылок для популярных загрузчиков 118
  47. Нестандартные загрузчики классов Схема поддержки загрузчиков в AOT: •  Во

    время AOT компиляции: –  Разрешение ссылок между классами согласно логики загрузчика •  Во время исполнения: –  Назначение каждому предкомпилированному классу соответсвующего инстанса загрузчика Работает для Eclipse RCP и Tomcat. В Spring Boot один загрузчик – все много проще. 119
  48. Java application Jar1 Jar2 Jar3 Executable Native code Resources Jar1

    Jar2 Jar3 Java Runtime JDK classes JVM Reflection GC JIT Meta data 120
  49. Промежуточный итог Хоть все и не просто, но это все

    реально работает! Вопрос, но как быстро? 121
  50. Промежуточный итог Хоть все и не просто, но это все

    реально работает! Вопрос, но как быстро? 122
  51. Шаблон DI @Service class MyServiceImpl implements MyService { public void

    doSomethingUseful() { //actual implementation } } 125
  52. Шаблон DI @Component class MyComponent { @Autowired MyService service; //

    MyServiceImpl will be injected void doBusinessLogic() { service.doSomethingUseful(); // интерфейсный // вызов (invokeinterface) ... } 126
  53. Шаблон DI @Component class MyComponent { @Autowired MyService service; //

    MyServiceImpl will be injected void doBusinessLogic() { 0: aload_0 1: getfield #2 // Field service:LMyService; 4: invokeinterface #3, 1 // IMethod MyService.doSomethingUseful:()V ... } } 127
  54. Типы вызовов в JVM •  invokestatic –  вызов статических методов

    •  invokespecial –  вызов конструкторов и суперметодов •  invokevirtual –  виртуальный вызов instance методов •  invokeinterface –  вызов методов инетерфейса •  invokedynamic –  переопределяемая логика резолва и вызова 128
  55. invokevirtual •  Вызываемый метод зависит от объекта получателя (this) • 

    Транслируется в машинный код как косвенный вызов через таблицу виртуальных методов invokevirtual <MethofRef> -> call [<VMTAddress> + MethodSlotInVMT*C] 129
  56. invokeinterface •  Вызываемый метод также зависит от объекта получателя (this)

    •  В худшем случае перед вызовом нужно произвести (линейный) поиск вызываемого метода! 131
  57. invokeinterface •  Очень важно оптимизировать! – inline cache – кэширование последних

    результатов поиска – превращение в invokevirtual через анализ типов ресиверов – превращение в прямой вызов (с последующей открытой подстановкой) 132
  58. Девиртуализация и деинтерфизация вызовов •  Предусловие дальнейшей открытой подстановки (inline)

    •  Анализ иерархии классов –  метод не перегружается – невиртуальный •  Типовый анализ –  new T().foo(); //вызов foo() // невиртуальный •  Profile guided 134
  59. MyService a; … è a.foo(); inlined body of foo() Идея:

    метод не перегружается – невиртуальный Анализ иерархии классов (CHA) 137
  60. MyService a; … è a.foo(); if (RT_Type(a) in CHA) {

    inlined body of foo() } else { a.foo(); //<- Сибирь } Идея: метод не перегружается – невиртуальный Анализ иерархии классов (CHA) 138
  61. Типовый анализ A a = b? new B() : new

    C(); a.foo(); Если foo() в B и С один и тот же, то a.foo() невиртуальный вызов (можно инлайнить) 140
  62. Типовый анализ A a = b? bar() : baz(); …

    a.foo(); Если bar() возвращает только new B, а baz() экземпляры new С, то a.foo() - опять невиртуальный вызов (можно инлайнить) 141
  63. MyService a; … è a.foo(); if (RT_Type(a) in PGO) {

    inlined body of foo() } else { a.foo(); //<- Сибирь } PGO 144 Девиртуализация на основе профилировки приложения
  64. Spring Boot Startup •  Dave Syer бенчмарк (https://github.com/dsyer/spring-boot-startup-bench) –  configserver

    – launched on Spring Boot 1.4 and 1.5 framework versions –  demo application – launched on Spring Boot 1.4, 1.5 and 2.0 versions –  minimal – launched on Spring Boot 1.5. Disabled auto configuration –  Petclinic – classic Petclinic sample launched on Spring Boot 1.5.9. –  Petclinic-latest – the same app launched on the latest Spring Boot (2.1.0) 147
  65. Spring Boot Startup 0% 20% 40% 60% 80% 100% 120%

    ConfigServer14x ConfigServer15x Minimal Petclinic Petclinic21x SpringBoot14x SpringBoot15x SpringBoot20x Average Fat jar HotSpot Fat jar JET Exploded HotSpot Exploded JET 149
  66. Spring Boot Startup Вопрос: Как синтетические класс файлы влияют на

    стартап. Ответ: Ускоряют (незначительно) Причина: Синтетические классы ~ в два раза меньше оригинальных – время на их синтез меньше, чем издержки на дальнейшее чтение 150
  67. Spring Boot Startup Вопрос: Как синтетические класс файлы влияют на

    стартап. Ответ: Ускоряют (незначительно) Причина: Синтетические классы ~ в два раза меньше оригинальных – время на их синтез меньше, чем издержки на дальнейшее чтение 151
  68. Spring Boot Startup Вопрос: Как синтетические класс файлы влияют на

    стартап. Ответ: Ускоряют (незначительно) Причина: Синтетические классы ~ в два раза меньше оригинальных – время на их синтез меньше, чем издержки на дальнейшее чтение 152
  69. Spring Boot Startup Вопрос: Как джитование динамически порожденных классов влияет

    на стартап. Ответ: Значительно. На текущий момент 40% времени старта Решение: Мы подумываем над реализацией интерпретатора 153
  70. Spring Boot Startup Вопрос: Как джитование динамически порожденных классов влияет

    на стартап. Ответ: Значительно. На текущий момент 40% времени старта Решение: Мы подумываем над реализацией интерпретатора 154
  71. Spring Boot Startup Вопрос: Как джитование динамически порожденных классов влияет

    на стартап. Ответ: Значительно. На текущий момент 40% времени старта Решение: Мы подумываем над реализацией интерпретатора 155
  72. Spring Boot Startup •  Spring Boot Startup – это производительность

    – Spring Boot на старте очень много чего делает – Очень много кода успевает прогреться – Изучение этого кода дает мотивацию для некоторых оптимизаций 156
  73. https://github.com/dsyer/spring-boot-aot •  Open JDK 8u181 Spring Boot Startup 2018-11-30 13:04:39.577

    INFO 12484 --- [ main] com.acme.SampleApplication : Started SampleApplication in 0.456 seconds (JVM running for 0.885) 159
  74. https://github.com/dsyer/spring-boot-aot •  Excelsior JET 15.3 (Java 8u181) 2018-11-30 12:59:28.155 INFO

    3300 --- [ main] com.acme.SampleApplication : Started SampleApplication in 0.028 seconds (JVM running for 0.167) Spring Boot Startup 160
  75. https://github.com/dsyer/spring-boot-aot •  Excelsior JET 15.3 (Java 8u181) 2018-11-30 12:59:28.155 INFO

    3300 --- [ main] com.acme.SampleApplication : Started SampleApplication in 0.028 seconds (JVM running for 0.167) Spring Boot Startup Опасайтесь замеров Хелловорлдов! 161
  76. Заключение •  Spring Boot очень удобная платформа для создания микросервисов.

    •  Не смотря на динамичность Spring, AOT вполне может справится с этой динамикой 165
  77. Заключение •  Чем больше микросервисов, тем важнее стартап тайм и

    производительность – CPU стоит денег в облаках •  AOT - многообещающий подход для ускорения старта и производительности 166
  78. Excelsior JET •  Бесплатная пробная версия (90 дней) – https://www.excelsiorjet.com/evaluate • 

    Бесплатная версия Standard Edition (32- бита) •  Team Blog – https://www.excelsiorjet.com/blog 167
  79. Ссылки на видео •  JVM: краткий курс общей анатомии: https://www.youtube.com/watch?v=-fcj6EL9rc4

    •  AOT компиляция Java: https://www.youtube.com/watch?v=aw89H-Vv-Zs •  Верифкация Java байткода: https://www.youtube.com/watch?v=m16AIz1fIFI •  Как сделать встроенный в JVM профайлер, который не боится AOT компиляции: https://www.youtube.com/watch?v=iw4O7G2eyHg 168