CONTENTS

Home
Updates
Software
Electronics
Music
Resume
Contact


YouTube
Twitter
GitHub
LinkedIn


Java Class Parser

Posted: October 5, 2015
Updated: May 17, 2023

Introduction

This project is a set of tools for doing a few things:

  • Dump class files into an easy to read format (similar to magic_elf and print_pe).
  • A small library of functions to build .class files both on disk and in memory.
  • Load a generated a class file contained in a byte[] array directly into a running JVM.

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

File Parsers: print_pe, magic_elf, dump_fat, amiga_recovery, dump_d64, java class

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:

  • coffee_dump: Similar to javap, but can output some more low level information about what's contained in a class file.
  • coffee_write: A test program that uses CoffeeWriter.cxx to create a simple class file.

With some supporting libraries:

  • CoffeeWriter.cxx: A class with a methods capable of creating a Java .class file in memory.
  • CoffeeMaker.java/cxx: This is a JNI/Java wrapper around CoffeeWriter.cxx.
  • Sample.java: A test program demonstrating code that can modify fields in TestData.java using a generated class / method.

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:

  • Sample.java
  • TestData.java
  • TestData.c

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:

JNI~828 cycles
Java Native ~1600 cycles
Java Native ByteBuffer ~50,000 cycles
Java Generated~7000 cycles
Java Generated Extended~1800 cycles

Using the -Xcomp command line option to force the JIT to always compile:

JNI~1400 cycles
Java Native ~216 cycles
Java Native ByteBuffer ~1188 cycles
Java Generated~3450 cycles
Java Generated Extended~432 cycles

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:

  1. Forcing the JVM JIT to always run when a method is called (using the command line option -Xcomp) makes the JNI code a lot slower.
  2. The Java generated code that access an external class is slower than JNI.
  3. The Java generated code using a class that extends the TestData class is faster than JNI, but despite being idental to Java Native, is still slower.

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
git clone https://github.com/mikeakohn/coffee_maker.git

Copyright 1997-2024 - Michael Kohn