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.