The hitchhiker’s guide to Java class reloading @antonarhipov
whoami Anton Arhipov @antonarhipov
whoami Anton Arhipov @antonarhipov
https://zeroturnaround.com/rebellabs/java-ee-productivity-report-2011/ 10% 20% 30% 0.5 1 2 3 4 5 6 7 8 10+ minutes
https://zeroturnaround.com/rebellabs/java-ee-productivity-report-2011/ 10% 20% 30% 0.5 1 2 3 4 5 6 7 8 10+ The only effective developers?? minutes
https://zeroturnaround.com/rebellabs/java-ee-productivity-report-2011/ 10% 20% 30% 0.5 1 2 3 4 5 6 7 8 10+ How about those? minutes
The problem: where did the time go?
The problem: where did the time go? 1-30 sec Container startup time
The problem: where did the time go? 1-30 sec Container startup time ~3 min 30 min1 s Application deployment
The problem: where did the time go? 1-30 sec Container startup time ~3 min 30 min1 s Application deployment ~1 min Navigation
Technical solutions
Technical solutions HotSwap
Technical solutions HotSwap Class loaders
Technical solutions HotSwap Class loaders Java agents & instrumentation
Technical solutions HotSwap Class loaders Java agents & instrumentation
hotswapEST. 2001
HOW ABOUT… refactoring?
JDB
instanceKlass constantPoolOop constants() pool_holder() klassVTable Embedded klassITable Embedded Embedded statics M. Dmitriev. Safe class and data evolution in large and long-lived Java (тм) applications. Technical report, Mountain View. 2001
instanceKlass constantPoolOop constants() constantPoolCacheOop cache() pool_holder() klassVTable Embedded klassITable Embedded Embedded statics M. Dmitriev. Safe class and data evolution in large and long-lived Java (тм) applications. Technical report, Mountain View. 2001
instanceKlass constantPoolOop constants() constantPoolCacheOop cache() pool_holder() klassVTable Embedded klassITable Embedded Embedded statics objArrayOop methodOop methods() M. Dmitriev. Safe class and data evolution in large and long-lived Java (тм) applications. Technical report, Mountain View. 2001
instanceKlass constantPoolOop constants() constantPoolCacheOop cache() pool_holder() klassVTable Embedded klassITable Embedded Embedded statics nmethod code() method() constants() objArrayOop methodOop methods() M. Dmitriev. Safe class and data evolution in large and long-lived Java (тм) applications. Technical report, Mountain View. 2001
instanceKlass constantPoolOop constants() constantPoolCacheOop cache() pool_holder() klassVTable Embedded klassITable Embedded Embedded statics nmethod code() method() constants() objArrayOop methodOop methods() M. Dmitriev. Safe class and data evolution in large and long-lived Java (тм) applications. Technical report, Mountain View. 2001 Limited to individual statements
What if… hotswap++
Dynamic Code Evolution for Java T. Würthinger, C. Wimmer, L. Stadler. 2010
Dynamic Code Evolution for Java T. Würthinger, C. Wimmer, L. Stadler. 2010 Statements
Dynamic Code Evolution for Java T. Würthinger, C. Wimmer, L. Stadler. 2010 Statements Methods
Dynamic Code Evolution for Java T. Würthinger, C. Wimmer, L. Stadler. 2010 Statements Methods Fields
Dynamic Code Evolution for Java T. Würthinger, C. Wimmer, L. Stadler. 2010 Statements Methods Fields Hierarchy
Dynamic Code Evolution for Java T. Würthinger, C. Wimmer, L. Stadler. 2010 Statements Methods Fields Hierarchy
Dynamic Code Evolution for Java T. Würthinger, C. Wimmer, L. Stadler. 2010 Statements Methods Fields Hierarchy + + + Binary-compatible
Dynamic Code Evolution for Java T. Würthinger, C. Wimmer, L. Stadler. 2010 Statements Methods Fields Hierarchy + + + x x x Binary-compatible Binary-incompatible
Classloaders
Class<?> uc1 = User.class; Class<?> uc2 = new DynamicClassLoader().load("com.zt.User"); out.println(uc1.getName()); // com.zt.User out.println(uc2.getName()); // com.zt.User out.println(uc1.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2 out.println(uc2.getClassLoader()); // com.zt.DynamicClassLoader@22b4bba7 User.age = 11; out.println((int) ReflectUtil.getStaticFieldValue("age", uc1)); // 11 out.println((int) ReflectUtil.getStaticFieldValue("age", uc2)); // 10 public class User { public static int age = 10; }
Class<?> uc1 = User.class; Class<?> uc2 = new DynamicClassLoader().load("com.zt.User"); out.println(uc1.getName()); // com.zt.User out.println(uc2.getName()); // com.zt.User out.println(uc1.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2 out.println(uc2.getClassLoader()); // com.zt.DynamicClassLoader@22b4bba7 User.age = 11; out.println((int) ReflectUtil.getStaticFieldValue("age", uc1)); // 11 out.println((int) ReflectUtil.getStaticFieldValue("age", uc2)); // 10 public class User { public static int age = 10; }
Class<?> uc1 = User.class; Class<?> uc2 = new DynamicClassLoader().load("com.zt.User"); out.println(uc1.getName()); // com.zt.User out.println(uc2.getName()); // com.zt.User out.println(uc1.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2 out.println(uc2.getClassLoader()); // com.zt.DynamicClassLoader@22b4bba7 User.age = 11; out.println((int) ReflectUtil.getStaticFieldValue("age", uc1)); // 11 out.println((int) ReflectUtil.getStaticFieldValue("age", uc2)); // 10 public class User { public static int age = 10; }
Class<?> uc1 = User.class; Class<?> uc2 = new DynamicClassLoader().load("com.zt.User"); out.println(uc1.getName()); // com.zt.User out.println(uc2.getName()); // com.zt.User out.println(uc1.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2 out.println(uc2.getClassLoader()); // com.zt.DynamicClassLoader@22b4bba7 User.age = 11; out.println((int) ReflectUtil.getStaticFieldValue("age", uc1)); // 11 out.println((int) ReflectUtil.getStaticFieldValue("age", uc2)); // 10 public class User { public static int age = 10; }
Class<?> uc1 = User.class; Class<?> uc2 = new DynamicClassLoader().load("com.zt.User"); out.println(uc1.getName()); // com.zt.User out.println(uc2.getName()); // com.zt.User out.println(uc1.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2 out.println(uc2.getClassLoader()); // com.zt.DynamicClassLoader@22b4bba7 User.age = 11; out.println((int) ReflectUtil.getStaticFieldValue("age", uc1)); // 11 out.println((int) ReflectUtil.getStaticFieldValue("age", uc2)); // 10 public class User { public static int age = 10; }
while(true) { Class<?> uc = new DynamicClassLoader().load("com.zt.User"); ReflectUtil.invokeStatic("getHobby", uc); } public class User { public Hobby getHobby() { return Basketball(); } }
while(true) { Class<?> uc = new DynamicClassLoader().load("com.zt.User"); ReflectUtil.invokeStatic("getHobby", uc); } public class User { public Hobby getHobby() { return Basketball(); } }
while(true) { Class<?> uc = new DynamicClassLoader().load("com.zt.User"); ReflectUtil.invokeStatic("getHobby", uc); } public class User { public Hobby getHobby() { return Basketball(); } } Assume there is a trigger / event
public static class Context { public HobbyService hobbyService = new HobbyService(); public void init() { hobbyService.user = new User(); anyService.initialize() } }
public static class Context { public HobbyService hobbyService = new HobbyService(); public AnyService anyService = new AnyService(); public void init() { hobbyService.user = new User(); anyService.initialize() } }
public static class Context { public HobbyService hobbyService = new HobbyService(); public AnyService anyService = new AnyService(); public void init() { hobbyService.user = new User(); anyService.initialize() } } while(true) { Class<?> c = new DynamicClassLoader().load("com.zt.Context"); Object context = c.newInstance(); ReflectUtil.invokeMethod("init", context); invokeService(context); }
DynamicClassLoader Context class HobbyService class User class Context object HobbyService object User object Reloadable “region” Live thread
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> DEMO
Java agents
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String args, Instrumentation inst) throws Exception { inst.addTransformer(new ClassFileTransformer { // here be dragons }); } }
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String args, Instrumentation inst) throws Exception { inst.addTransformer(new ClassFileTransformer { // here be dragons }); } } META-INF/MANIFEST.MF Premain-Class: Agent
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; public class Agent { public static void premain(String args, Instrumentation inst) throws Exception { inst.addTransformer(new ClassFileTransformer { // here be dragons }); } } $> java –javaagent:agent.jar application.Main META-INF/MANIFEST.MF Premain-Class: Agent
ClassFileTransformer ClassA ClassA ClassA0 +field1 +field2 +field3 +method1 +method2 +method3 +method1 +method2 +method3 +field1 +field2 +field3 +proxy_methods A thousand years of productivity: the JRebel story E. Kabanov, V. Vene, 2012
ClassFileTransformer ClassA ClassA ClassA0 +field1 +field2 +field3 +method1 +method2 +method3 +method1 +method2 +method3 +field1 +field2 +field3 +proxy_methods A thousand years of productivity: the JRebel story E. Kabanov, V. Vene, 2012
What did we learn today?
What did we learn today? HotSwap - a feature of your JVM, not IDE!
What did we learn today? HotSwap - a feature of your JVM, not IDE! Class loaders - easy to implement, but limited
What did we learn today? HotSwap - a feature of your JVM, not IDE! Class loaders - easy to implement, but limited Java agents & instrumentation - very tricky!
anton@zeroturnaround.com @antonarhipov slideshare.net/arhan

JavaOne 2017 - The hitchhiker’s guide to Java class reloading