@antonarhipov
Java class
The hitchhiker’s guide to
reloading
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
HotSwap
Class loaders
Java agents & instrumentation
Challenges
hotswapEST. 2001
HOW ABOUT…
refactoring?
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
What if…
hotswap++
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();
}
}
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
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
public class C extends X {
int y = 5;
int method1(int x) {
return x + y;
}
void method2(String s) {
System.out.println(s);
}
}
public class C extends X {
int y = 5;
int method1(int x) {
Object[] o = new Object[1];
o[0] = x;
return Runtime.redirect(this, o, "C", "method1", "(I)I");
}
void method2(String s) {
Object[] o = new Object[1];
o[0] = s;
return Runtime.redirect(
this, o, "C", "method2", "(Ljava/lang/String;)V");
}
}
public class C extends X {
int y = 5;
int method1(int x) {
Object[] o = new Object[1];
o[0] = x;
return Runtime.redirect(this, o, "C", "method1", "(I)I");
}
void method2(String s) {
Object[] o = new Object[1];
o[0] = s;
return Runtime.redirect(
this, o, "C", "method2", "(Ljava/lang/String;)V");
}
}
public class C extends X {
int y = 5;
int method1(int x) {
Object[] o = new Object[1];
o[0] = x;
return Runtime.redirect(this, o, "C", "method1", "(I)I");
}
void method2(String s) {
Object[] o = new Object[1];
o[0] = s;
return Runtime.redirect(
this, o, "C", "method2", "(Ljava/lang/String;)V");
}
}
public abstract class C0 {
public static int method1(C c, int x) {
int tmp1 = Runtime.getFieldValue(c, "C", "y", "I");
return x + tmp1;
}
public static void method2(C c, String s) {
PrintStream tmp1 = Runtime.getFieldValue(
null, "java/lang/System", "out", "Ljava/io/PrintStream;");
Object[] o = new Object[1];
o[0] = s;
Runtime.redirect(
tmp1, o, "java/io/PrintStream;", "println",
"(Ljava/lang/String;)V");
}
}
int method1(int x) {
return x + y;
}
public abstract class C0 {
public static int method1(C c, int x) {
int tmp1 = Runtime.getFieldValue(c, "C", "y", "I");
return x + tmp1;
}
public static void method2(C c, String s) {
PrintStream tmp1 = Runtime.getFieldValue(
null, "java/lang/System", "out", "Ljava/io/PrintStream;");
Object[] o = new Object[1];
o[0] = s;
Runtime.redirect(
tmp1, o, "java/io/PrintStream;", "println",
"(Ljava/lang/String;)V");
}
}
public class C extends X {
int y = 5;
int method1(int x) {
return x + y;
}
//...
}
public class C extends X {
int y = 5;
int z() {
return 10;
}
int method1(int x) {
return x + y + z();
}
//...
}
public class C1 {
public static int z(C c) {
return 10;
}
public static int method1(C c, int x) {
int tmp1 = Runtime.getFieldValue(c, "C", "y", "I");
int tmp2 = Runtime.redirect(c, null, "C", "z", "(V)I");
return x + tmp1 + tmp2;
}
//...
}
WHAT ABOUT…
configuration?
DEMO
HotSwap
Class loaders
Java agents & instrumentation
anton@zeroturnaround.com
@antonarhipov

Riga DevDays 2017 - The hitchhiker’s guide to Java class reloading

  • 1.
  • 2.
  • 5.
  • 6.
  • 7.
  • 8.
    HotSwap Class loaders Java agents& instrumentation Challenges
  • 9.
  • 17.
  • 20.
  • 21.
  • 22.
    Dynamic Code Evolutionfor Java T. Würthinger, C. Wimmer, L. Stadler. 2010 Statements Methods Fields Hierarchy + + + x x x Binary-compatible Binary-incompatible
  • 24.
  • 25.
    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; }
  • 26.
    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; }
  • 27.
    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; }
  • 28.
    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; }
  • 29.
    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; }
  • 30.
    while(true) { Class<?> uc= new DynamicClassLoader().load("com.zt.User"); ReflectUtil.invokeStatic("getHobby", uc); } public class User { public Hobby getHobby() { return Basketball(); } }
  • 31.
    while(true) { Class<?> uc= new DynamicClassLoader().load("com.zt.User"); ReflectUtil.invokeStatic("getHobby", uc); } public class User { public Hobby getHobby() { return Basketball(); } }
  • 32.
    public static classContext { public HobbyService hobbyService = new HobbyService(); public void init() { hobbyService.user = new User(); anyService.initialize() } }
  • 33.
    public static classContext { public HobbyService hobbyService = new HobbyService(); public AnyService anyService = new AnyService(); public void init() { hobbyService.user = new User(); anyService.initialize() } }
  • 34.
    public static classContext { 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); }
  • 35.
    DynamicClassLoader Context class HobbyService class Userclass Context object HobbyService object User object Reloadable “region” Live thread
  • 38.
  • 43.
    import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; publicclass 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
  • 44.
  • 45.
    public class Cextends X { int y = 5; int method1(int x) { return x + y; } void method2(String s) { System.out.println(s); } }
  • 46.
    public class Cextends X { int y = 5; int method1(int x) { Object[] o = new Object[1]; o[0] = x; return Runtime.redirect(this, o, "C", "method1", "(I)I"); } void method2(String s) { Object[] o = new Object[1]; o[0] = s; return Runtime.redirect( this, o, "C", "method2", "(Ljava/lang/String;)V"); } }
  • 47.
    public class Cextends X { int y = 5; int method1(int x) { Object[] o = new Object[1]; o[0] = x; return Runtime.redirect(this, o, "C", "method1", "(I)I"); } void method2(String s) { Object[] o = new Object[1]; o[0] = s; return Runtime.redirect( this, o, "C", "method2", "(Ljava/lang/String;)V"); } }
  • 48.
    public class Cextends X { int y = 5; int method1(int x) { Object[] o = new Object[1]; o[0] = x; return Runtime.redirect(this, o, "C", "method1", "(I)I"); } void method2(String s) { Object[] o = new Object[1]; o[0] = s; return Runtime.redirect( this, o, "C", "method2", "(Ljava/lang/String;)V"); } }
  • 49.
    public abstract classC0 { public static int method1(C c, int x) { int tmp1 = Runtime.getFieldValue(c, "C", "y", "I"); return x + tmp1; } public static void method2(C c, String s) { PrintStream tmp1 = Runtime.getFieldValue( null, "java/lang/System", "out", "Ljava/io/PrintStream;"); Object[] o = new Object[1]; o[0] = s; Runtime.redirect( tmp1, o, "java/io/PrintStream;", "println", "(Ljava/lang/String;)V"); } } int method1(int x) { return x + y; }
  • 50.
    public abstract classC0 { public static int method1(C c, int x) { int tmp1 = Runtime.getFieldValue(c, "C", "y", "I"); return x + tmp1; } public static void method2(C c, String s) { PrintStream tmp1 = Runtime.getFieldValue( null, "java/lang/System", "out", "Ljava/io/PrintStream;"); Object[] o = new Object[1]; o[0] = s; Runtime.redirect( tmp1, o, "java/io/PrintStream;", "println", "(Ljava/lang/String;)V"); } }
  • 51.
    public class Cextends X { int y = 5; int method1(int x) { return x + y; } //... } public class C extends X { int y = 5; int z() { return 10; } int method1(int x) { return x + y + z(); } //... }
  • 52.
    public class C1{ public static int z(C c) { return 10; } public static int method1(C c, int x) { int tmp1 = Runtime.getFieldValue(c, "C", "y", "I"); int tmp2 = Runtime.redirect(c, null, "C", "z", "(V)I"); return x + tmp1 + tmp2; } //... }
  • 53.
  • 54.
  • 55.
  • 56.