Java Class ParserPosted: October 5, 2015 Introduction This project is a set of tools for doing a few things:
The point of the project was to test if a Java method could be generated, similar to a JIT on a native CPU but in this case generate Java byte-code, and load it into a running JVM. The idea was to see if this generated code could be faster than using JNI to fill in fields in a class. On a side note, I think it might actually be possible to create a full Java class in memory with byte-code using Java itself, but this library would be useful to me for other future projects. Related Projects @mikekohn.net
The Tools All the code is available from github in the link at the bottom of the page. The project was called CoffeeMaker, mostly because I was in a hurry because to test out my theory and couldn't come up with anything better in a short amount of time. The tools in the repo are:
With some supporting libraries:
Building To build these tools, on a Linux, MacOS, BSD, or Windows system that has mingw tools:
git clone https://github.com/mikeakohn/coffee_maker.git
cd coffee_maker
make
make jni
sh run_sample.sh
To do the "make jni" step, OpenJDK should be installed. The Makefile in the build/ directory has a variable called JNI_PATH which might have to be changed to point to where OpenJDK is installed. It's set up right now for OpenJDK 8 on the latest Ubuntu. Explanation The reason I started this project was to see if there was a way to populate the fields of a Java object in a faster way than using JNI. Maybe this sounds odd, but I found myself in a situation where the fields of a class that need to be updated are not known until runtime. The sample program in the sample/ directory (along with the run_sample.sh script in the root dir) should give an idea how to use the JNI library. There are 3 files here:
TestData.java has 20 Java fields defined named field0 to field19. Sample.java takes 20 int arguments from the command line and populates a byte[] array called data with the data in little endian format. Using five different ways, the byte[] data populates the TestData's fields. The different methods are: 1) Using a JNI function that for all 20 fields does:
num = *((int *)(&data[ptr]));
(*env)->SetIntField(env, obj, field_var0, num);
2) Java Native code that for all 20 fields rebuilds the int with 4 bytes :
field0 = data[76] | (data[77] << 8) | (data[78] << 16) | (data[79] << 24);
3) Java Native code that for all 20 fields uses ByteBuffer:
field0 = byte_buffer.getInt(ptr);
4) Generate TestClass.class object in memory, generate a method for populating the fields of TestData that looks almost identical to Java Native code described in method 2 except it takes a TestData object as a parameter and does test_data.field0 = (code to do the shifting of bytes into an int). 5) Generate TestClass.class the same as method 4 except this time TestClass will extends TestData so this time the fields can be accessed as members of the TestClass object instead of needing to reference the public fields of an external class. After running the run_sample.sh, I'm seeing the following performance numbers without the -Xcomp command line option:
Using the -Xcomp command line option to force the JIT to always compile:
OpenJDK 8 is being used here, so not sure if newer Java runtimes would come up with different performance numbers, but what I noticed some interesting things:
Since the class writing part of the project is contained in a single ClassWriter.cxx module, I now have ideas for other projects that could actually use this. Maybe a simple scripting language that outputs Java .class files or such? Source code
Copyright 1997-2024 - Michael Kohn
|