Having fun with java.lang.Object

java.lang.Object is the mother of all objects in the Java world. While being the core of Java, it is not a native object, but rather stored as compiled class file that is located in $JAVA_HOME/jre/lib/rt.jar. This means that if you try overwriting the file at java/lang/Object.class within the jar file, your Object will get loaded instead of the original Object definition.

The idea here is to overwrite java.lang.Object which will give you first access to any other object before anybody else. However, it’s probably not a wise idea to modify the original jar file itself, as Java will break if you make some bad changes to rt.jar. I recommend that you copy rt.jar instead, and tell java to use it instead of the original by using the -Xbootclasspath flag. For this purpose, I’d assume that you’ve named your new jar file as modified-rt.jar.

Now look for src.zip in your $JAVA_HOME directory and extract java/lang/Object.java. Compile that and add the class file into your new jar:

$ javac java/lang/Object.java
$ jar uvf  modified-rt.jar java/lang/Object.class

The compiled class won’t be any different from the standard lang.Object within the java class. (Do a ‘diff’ on it if you like). A triva on the compiled java.lang.Object, is that javac actually treats it differently when it comes to compiling the constructor. If you run javap on it, you’ll find out that it doesn’t have an INVOKESPECIAL constructor call that is present for all other objects, an implicit constraint that is defined by the Java Machine Specification. It made sense, after all there is no other object that is its superclass.

To make sure that the new modified-rt.jar runs ok, write a test application to make sure that the JVM will run as normal. I’m going to use the following example class file, and use it illustrate what I’m going to do later, so you might want to do the same as well. Save the following file as ObjectFieldReflection.java:

import java.lang.reflect.*;

public class ObjectFieldReflection {
 public static void main(String args[]) throws Exception {
   Field[] f_a = Object.class.getDeclaredFields();
   for (Field f : f_a) {
     f.setAccessible(true);
     System.out.println("Fieldname="+f.getName()+" value="+f.get(null));
   }
 }
}

The above application will try to cycle through all the fields of java.lang.Object and print out the value within the fields. Since the default java.lang.Object doesn’t have any fields at all, there’s really nothing to print out, and the application will just terminate as normal:

$ java -Xbootclasspath:rt.jar ObjectFieldReflection

One of the things that may be fun to do is to count the number of objects that is instantiated by Java over the lifecycle of the application. An easy way to do that is to create a static field in java.lang.Object, and use the default constructor to count every time an object is created. Edit the java/lang/Object.java file, and add the following static field declaration and a default constructor so that it looks like this:

public static int createdCount = 0;

public Object() {
 createdCount++;
}

Recompile and re-add that into the jar file, and run it again. Now you should be able to find out the number of objects created in the lifecycle of your application, which the output looks something like this:

$ java -Xbootclasspath:rt.jar ObjectFieldReflection
Fieldname=createdCount value=1200

It tells you that 1200 objects have been created just for the example simple application to run. Imagine how many more classes are created for a large application? Let’s say you want to find out about the number of objects garbaged in the lifecycle of your application instead. Modify Object.java like this:

public static int garbagedCount = 0;

public void finalize() {
 garbagedCount++;
}

finalize() is a special method that all Java object calls before being garbaged collected. It is similar to destructors in C++, although there are many subtle differences between them in reality. If you tried running with the new code, the JVM crashes with a core dump:

#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  SIGSEGV (0xb) at pc=0xb79df93f, pid=25096, tid=3085408944
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_08-b03 mixed mode)
# Problematic frame:
# V  [libjvm.so+0x30793f]
#
# An error report file with more information is saved as hs_err_pid25096.log
#
# If you would like to submit a bug report, please visit:
#   http://java.sun.com/webapps/bugreport/crash.jsp
#
Aborted

So what’s going on with that? Your guess is as good as mine, but here’s what I think: Java has to rely on the Garbage Collector(GC) to handle memory collection and because the GC uses complex algorithms to make reclaiming memory efficient, it is probably expecting that java.lang.Object is by default the easily reclaimed sort. But since we’re putting code into finalize(), it actually changes that assumption, since all objects have to be individually ‘collected’, thus requiring the less efficient ‘Mark and Sweep’ GC method, something that the JVM is not expecting, hence crashing it.

So while tampering with Java core class files may be a nice, unintrusive way of collecting information without having to modify existing applications, or applications which you do not have the source to, be mindful that sometimes it may not be worth the trouble when it comes to dealing with the unintended consequences that arise, especially when coded in a manner that contravene the rules of the Java specification.