More uses for dynamic code in Java.
In 2008 I wrote a library for compiling and running dynamic code in Java. Essence JCF At the time the purpose was to load configuration files which were written in Java rather than XML or properites files.
One advantage this library has is that it can load into the current class loader, rather than requiring an additonal class loader so the interface or class can be used immediately in code without the need for reflection.
See below for an example.
For me, it has been a very cool solution without a unique problem to solve. i.e. there wasn't a problem it solved particularly well.
Since then, I have come across a few situations where it is particularly useful.
Using dynamically generated code, you can build a data store from an interface which is row based or column based, stored either in the heap or in direct memory. Both can reduce the number of objects created improving cache locality and reducing GC times.
Expressions which are executed often can be pre-compiled and is more open to low-level JVM optimisation like inlining.
Anywhere a large percentage of the work is performing a loop to call other components/nodes. esp. if reflection is used to call them. This can be expanded into one method which calls all the methods involved.
When multiple reflection calls are made, it can be faster to replace the code with a single block which does the same thing using native code. Your generated code can be package local to any package you need. ;)
Dynamic proxies are efficient, but they still need to turn an argument list into an array, primitives into objects, and you need to decode the method to determine what to do for that method, esp Object's methods. With a dynamically generated proxy, those issues are avoided naturally by standard programming technics.
In this case, the class is loaded from memory. The in memory code "replaces" a version which was available to be compiler at compile time. This means it can be used in code naturally, but executes the code loaded dynamically.
Note: When the code is run in the debugger, the class is written to a directory available to be IDE. This means you can step into and debug the dynamically generated code, but it doesn't have to be on disk when the application runs normally.
Debugging dynamically generated code can be real head ache otherwise.
One advantage this library has is that it can load into the current class loader, rather than requiring an additonal class loader so the interface or class can be used immediately in code without the need for reflection.
See below for an example.
For me, it has been a very cool solution without a unique problem to solve. i.e. there wasn't a problem it solved particularly well.
Since then, I have come across a few situations where it is particularly useful.
Objects in direct memory
Using dynamically generated code, you can build a data store from an interface which is row based or column based, stored either in the heap or in direct memory. Both can reduce the number of objects created improving cache locality and reducing GC times.
Precompile expressions
Expressions which are executed often can be pre-compiled and is more open to low-level JVM optimisation like inlining.
Loop unrolling
Anywhere a large percentage of the work is performing a loop to call other components/nodes. esp. if reflection is used to call them. This can be expanded into one method which calls all the methods involved.
A replacement for reflection
When multiple reflection calls are made, it can be faster to replace the code with a single block which does the same thing using native code. Your generated code can be package local to any package you need. ;)
Replace dynamic proxies
Dynamic proxies are efficient, but they still need to turn an argument list into an array, primitives into objects, and you need to decode the method to determine what to do for that method, esp Object's methods. With a dynamically generated proxy, those issues are avoided naturally by standard programming technics.
Example of a dynamically loaded configuration for a component
In this case, the class is loaded from memory. The in memory code "replaces" a version which was available to be compiler at compile time. This means it can be used in code naturally, but executes the code loaded dynamically.
Note: When the code is run in the debugger, the class is written to a directory available to be IDE. This means you can step into and debug the dynamically generated code, but it doesn't have to be on disk when the application runs normally.
Debugging dynamically generated code can be real head ache otherwise.
// this writes the file to disk only when debugging is enabled.
CachedCompiler cc = CompilerUtils.DEBUGGING ?
new CachedCompiler(new File(parent, "src/test/java"), new File(parent, "target/compiled")) :
CompilerUtils.CACHED_COMPILER;
String text = "generated test " + new Date();
Class fooBarTeeClass = cc.loadFromJava("eg.FooBarTee", "package eg;\n" +
'\n' +
"import eg.components.BarImpl;\n" +
"import eg.components.TeeImpl;\n" +
"import eg.components.Foo;\n" +
'\n' +
"public class FooBarTee{\n" +
" public final String name;\n" +
" public final TeeImpl tee;\n" +
" public final BarImpl bar;\n" +
" public final BarImpl copy;\n" +
" public final Foo foo;\n" +
'\n' +
" public FooBarTee(String name) {\n" +
" // when viewing this file, ensure it is synchronised with the copy on disk.\n" +
" System.out.println(\"" + text + "\");\n" +
" this.name = name;\n" +
'\n' +
" tee = new TeeImpl(\"test\");\n" +
'\n' +
" bar = new BarImpl(tee, 55);\n" +
'\n' +
" copy = new BarImpl(tee, 555);\n" +
'\n' +
" // you should see the current date here after synchronisation.\n" +
" foo = new Foo(bar, copy, \"" + text + "\", 5);\n" +
" }\n" +
'\n' +
" public void start() {\n" +
" }\n" +
'\n' +
" public void stop() {\n" +
" }\n" +
'\n' +
" public void close() {\n" +
" stop();\n" +
'\n' +
" }\n" +
"}\n");
// add a debug break point here and step into this method.
FooBarTee fooBarTee = new FooBarTee("test foo bar tee");
Foo foo = fooBarTee.foo;
assertNotNull(foo);
assertEquals(text, foo.s);
Comments
Post a Comment