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.