Showing posts with label JVM. Show all posts
Showing posts with label JVM. Show all posts
Saturday, June 05, 2010

Why a volatile is different in C/C++ and Java

The volatile keyword is highly subjective to the language and the platform it is implemented on. While Java provides a consistent behaviour of volatile across all architectures, this is not the case for languages that are directly compiled into the native machine platform, such as in the case of C/C++. Let's try to understand why this is the case.

Let a,  b be member of a set of program actions P, and v_{n} (a) be function that applies the volatility requirement to an action, where the subscript _n denotes the _n-th iteration in which a volatile action is applied, and \rightarrow be the precedes operator, which is explained previously. For all program actions, the following rules holds:

v_n(a) \rightarrow v_{n+1}(a)

a \rightarrow v_n(b)  \Rightarrow a \rightarrow v_{n+i}(b) where  i \in \mathbb{N}

Rule 1 says that all volatile functions enforce a total order, where the function v_{n} (a) always precedes v_{n+1} (a), where rule 2 says that if an action a precedes a volatile function on an action b on the _n-th iteration, then the action a must necessarily precede all subsequent volatile functions applied to b.

This is a very strong memory requirement in Java, in fact it is much stronger than compared to C/C++. The C/C++ language specification has no such restriction on memory ordering, and leaves it to the compiler implementation to decide how non-volatile actions are ordered around volatile actions.

Let's consider how the these rules affect program execution with a simple code sample:


int a = 0;
int b = 0;
volatile int count = 0;

a = 1;
count = 1;
b = 2;
count = 2;


In C/C++, the volatile keyword only guarantees that the count variable cannot be reordered against each other, ie. if count == 2, then count = 1 must necessarily precede it. However, there is neither a guarantee that a == 1, nor that b == 2.

In Java, given the stronger guarantee defined above, then if count == 1, then the assertion a == 1 must be true. Similarly, if count == 2 then assertion that a == 1 && b == 2 must be true. This is what is means by the strict memory guarantee that Java offers that C/C++ does not.

However this does not mean that C/C++ will not behave the same way Java does. Whether if it does so depends on (1) whether if the compiler performs any code reordering that may be in a surprising order, but legit order, and (2) whether if the underlying machine architecture upholds the same strict memory order, provided that the compiler does not perform any surprising code reordering.

For instance, compiling code on gcc with -O0 set on all x86 platforms will conform to (and is stricter than) Java's memory model, but other architectures such as the PowerPC, Alpha, Itanium all uphold a weaker memory model which can exhibit surprising program behaviours that a programmer may not expect. Caveat Lector!

Anyhow, if you are interested in more memory model consistency rules, you might want to watch Intel's explanation of the x86 memory model where the nuances of memory ordering is explained in good detail. Enjoy!

Saturday, May 29, 2010

Word Tearing

... Or why you are not guaranteed a coherent value when you are reading longs and doubles in Java.

How is that possible?

This mainly due to a language compromise to deal with 'legacy' 32-bit CPU systems, where a 64-bit value memory action is divided into two 32-bit memory actions, which is mainly done for historical efficiency reasons (Refer to 17.4 of the Java Language Specification for an explicit answer to why). But note that this is not a problem if you are using native 64-bit architectures.

So how does this translate to a possible problem?

Lets try to simplify this problem by illustrating the same scenario as having a 2-bit value memory action being split into two 1-bit memory action. So assuming that we have a memory location m which looks something like this:



From the diagram m is logically separated into to two 1-bit locations m[0] and m[1]. Let's assume that m starts off with 00b as its initial value. Then m looks like this:



Now suppose that we have 2 CPUs concurrently executing the following code:

CPU1CPU2
R(m)W(m=11b)

R(m) is a function that reads the value of m, while W(m) is a function that sets the value to m. In the case of our example, CPU1 is reading the value of m while CPU2 is writing the binary value 11b in to m.

Logically if atomicity is enforced, then the only possible value that can be observed at any given time is either 00b or 11b. But in the case of longs and doubles, the specification explicitly mentions that no guarantees of atomicity is enforced.

Hence it is possible to timeslice memory updates in the following order:



As CPU2's execution is timesliced between the execution of CPU1, this results in R(m) return the value of 01b, which is a transient value, but it is not a proper value to be observed.

It can be said that that CPU1 happens to be reading at the right place but at a wrong time. The volatile keyword in Java will resolve this problem, by virtue of the fact that it does not allow reordering of read and write actions, and hence by implictly disallowing read in-between write actions and vice-versa.

This has the effect of making read and write actions appear atomic, not that the read and write actions are implicitly atomic themselves. For a better understanding of what I mean by atomic, see my explanation on the difference between Atomic and Volatile.
Monday, May 03, 2010

The Implementation of Volatile in the JVM

Scanning through the code between the JVM bytecode interpreter and the JIT compiler, I was perplexed to why an interpreter does not care to differentiate between a volatile memory field access to a non-volatile one, while a JIT actually emits special code to deal with it.

But after thinking though a while, I realise why this actually makes sense. Before we find out why an interpreter does not need to care about volatile semantics, we need to first understand the idea of causality, and then understand what the volatile keyword is for.

Lets start off with a very simple snippet of code:


a = 1
v1 = a


We expect v1 to have 1 because we put 1 into a, and then a into v1. The assignment of a = 1 necessarily orders before the operation of v1 = a. This necessity is termed as 'causal order'. Subsequently we use the notation a = 1 \rightarrow v1 = a to imply this causal ordering.

For illustration, we can show that not all program executions need to follow causal order:


a = 1
b = 2
v1 = a
v2 = b


Assuming that v1 and v2 needs to be assigned the value of a and b eventually, then there is a causal order such that the execution of the statement a = 1 \rightarrow v1 = a, and a causal order of b = 2 \rightarrow v2 = b. However there is no such requirement needed between the statement of a = 1 and b = 2. In fact, if we swapped the order around, the program will continue to execute fine:


b = 2
a = 1
v1 = a
v2 = b


As long as we don't break causal ordering, executions that has no causal order requirements have an interesting property of parallelism. We are able to break the such programs into separate parts and execute them in parallel and still be able to satisfy program requisites:

CPU1
CPU2
a = 1
b = 2
v1 = a
v2 = b

But with multi-processors, we can violate program determinism quite easily if we are not careful. Consider the following example:

CPU1
CPU2
a = 1
a = 2
v1 = a


In the absence of any ordering restrictions, we term the execution as concurrent; it is legal for the value of v1 to be either 1 or 2. In practice this value most likely be 1 because of cache locality, but this is not a guarantee. Even though executions between CPU1 and CPU2 can happen at exactly the same quantum, we treat such concurrent executions as if they are a special case of interleaving; either CPU1's executions always happen slightly before CPU2's executions, or CPU2's executions always happen slightly before CPU1's executions. Corollary: CPU1's and CPU2's executions never happen simultaneously, hence can be considered to be functionally equivalent to a multi-threaded application. This resulting execution is known to have a partial-ordering, ie, the execution between CPU1 and CPU2 can be interleaved in any possible permutations.

For compiled languages like C/C++, there is no implicit notion of concurrency; this is also true for a just-in-time compiler within Java. No notion of concurrency is fine and dandy on a 1-CPU machine architecture, but not so on a multi-processor system or in a language that has a notion of threads. Let's understand why by first examining a simple single-threaded code:


a = 1
a = 2
v1 = a
v2 = a
if ( v1 == v2 ) {
v3 = true
}
return v3


If causal order is upheld, then naturally a = 1 \rightarrow a = 2 \Rightarrow v1 == v2 == a == 2. On a single-threaded execution that is causally ordered, the resulting execution is also totally ordered. Hence, an optimizing compiler may say, "hey, why don't I skip all the steps and assign v3 = true directly, since v1 == v2 always holds?". In the end, the compiler generates the code that looks like this:


v3 = true;
return v3; // or simply "return true;"


Now consider a similar, but multi-threaded code:

T1
T2
a = 1
a = 0
v1 = a
a = 2
v2 = a

if ( v1 == v2 )

{

v3 = true

}

return v3


Notice that both threads T1 and T2 still follows causal order. For T1, a = 1 \rightarrow v1 = a \rightarrow v2 = a, hence implies that v1 == v2 == 1. On T2, a = 0 \rightarrow a = 2 is shown as a trivial case of causal order. However, there is no total ordering enforced among the execution between the two threads.

For a given compiler, it is very difficult to be able to reason the ordering of execution across a number of threads, given each thread's execution sequence is normally examined in isolation. The reason for this limitation is simple: in order for a compiler to be able to optimise for causality safety, it needs to examine all executions across all threads; however it is theoretically possible for the number of threads to be (1) dynamically generated; (2) the number of threads generated to be unbounded and therefore infeasible to make such an analysis.

Usually the compiler will optimise on the assumption that causality safety is assumed, where the onus is on the programmer to indicate otherwise through the volatile keyword.

In the absence of the volatile keyword, the compiler is free to assume the following optimisation:

T1
T2
v3 = true
// does nothing, since a is unused
return v3; // or simply "return true";


Therefore, the volatile keyword means for all accesses of a, always emit the code for the accesses. By doing so, it retains the code's original execution and hence execution coherency.

This is also why the volatile semantics within an interpreter is always upheld.

As an interpreter always execute instructions line-by-line, therefore no inferences and optimisations actually occurs compared to compiled code. Thus the interpreter needs not enforce the volatile mechanic, so as long as the underlying machine architecture adheres to program causality.

And this behaviour should be safe on machine architectures like the x86, but on esoteric machine architectures that performs instruction reordering, eg. Alpha and Itanium, the interpreter may require some additional memory fencing instructions to ensure that volatile semantics are upheld.
Wednesday, July 22, 2009

Java is not the JVM

For many IT people, it sounds funny to assert that the Java language has nothing to with the JVM itself. But as incredulous as it sounds, this is actually true. Let me explain, using some code as a shallow illustration how this is the case.

When I was hacking at the Java bytecode level, one of the things that I do is to optimise for memory efficiency. There is a need for storing an array of booleans, and the most obvious way of saving memory is to store it at a bitwise level, by stashing 8 boolean values within a byte.

Within the JVM, booleans are stored as bytes (executionally, they are worse: the VM treats booleans as ints!). Furthermore, in Java, there isn't a low-level means of utilising booleans as integral types like C can. If you had to write code in pure Java, at best you'll end up writing code like this:


// assume z == boolean[8]
byte b = 0;
for ( int i=0; i < 8; i++ ) {
if ( z[i] == true ) {
b |= ( 1
<< i );
}
}



Unlike C, the code is clunky, as you are having to perform a conditional check on a boolean, before you can perform bitwise operations on the values, because Java considers booleans as a non-integral type. How annoying!

But this constrain only affects the Java language - the same rules do not apply when it comes to the JVM. On the VM, it is perfectly legit for you to express code like this:


// assume z == boolean[8]
byte b = 0;
for ( int i=0; i < 8; i++ ) {
b |= z[i] << i;
}


However, just about any Java compiler disallows this code to compile - the operations on the boolean violates type-safety. But don't blame the compilers, they are just conforming to the language specifications. But since the JVM has nothing to do with the Java language, there is nothing illegal in doing so outside the Java language, let say by using bytecode assembly. Here's an equivalent, using jasmin assembly code:

.source BooleanToByte.j
.class BooleanToByte
.super java/lang/Object

.method public static main([Ljava/lang/String;)V
.limit stack 4
.limit locals 3

iconst_0
istore_1 ; byte b = 0;

iconst_0
istore_2 ; int i = 0;

LOOP:

iload_2
bipush 8
if_icmpge EXIT_LOOP: ; if i>=z.length exit loop

; here's the magic code that allows you to do direct
; bitwise
b |= z[i] << b="">
iload_1
aload_0
iload_2
baload
iload_2
ishl
ior
istore_1

iinc 2 1
goto LOOP:

LOOP_EXIT:

return

.end method

The jasmin code will probably assemble, but don't expect the JVM to execute it; it serves only as an example, and lacks a few things (I'm missing the constructor block and other nitty gritty little things that's needed to satisfy the bytecode verifier). It is but a case study to separate the JVM from the Java language as people typically assume.

There has been a number of other languages that has since mushroomed which relies on the JVM as its core; these languages include Groovy, Scala, Jython and JRuby, many of which are rather interesting, although they are more of a curiosity at this stage - I've yet to see any of these implementations deployed in a production environment, although I don't say that as a criticism of any of these languages. In fact, I am actually quite impressed with the JRuby, and I recommend you give it a try. It's very faithful to the actual Ruby implementation and allows you to use Java directly. Good fun, I'd say, especially when it combines the expressive of the former with the features of the latter. It's quite impressive that the JVM has been able to be so versatile in allowing other languages to plug into it directly.
Saturday, January 26, 2008

Circumventing Java's Initialization Process

This is really not a circumvention per se, but rather an understanding of how the Java initialization behaviour works, and highlight the violation of the JVM specification, even in Sun's version, so that Java will be able to function pragmatically. The JVM specification (§2.17.4) indicates that:
A class or interface type T will be initialized immediately before one of the following occurs: * T is a class and an instance of T is created. [...]
This means that if object T is being created, then the class of T will have to be initialized. But is it always being upheld? For a fact, it's never upheld in a cyclic self-referencing case:


public class T {
int value;
T my_object;

static {
my_object = new T();
my_object.print();
value = 1;
}

public void print() {
System.out.println("The value is " + value);
}
}


If you ran the application, even after the object for T is being created, the value returned is '0' rather than '1' which violates the definition as described above. If this rule is not violated, if I am correct, on older JVMs like 1.0, this will translate into a cyclic situation, as the object creation will trigger it's object constructor, which will trigger the class constructor, which it is already in, and so on.

So what's the problem with that? The reason why initialization is important, is so that you'll expect a reliable value to be set by the time you use the class/object, which may not be necessarily be the case if that constraint is relaxed. Consider a fringe example like this:



// ---- DualWait1.java -----

public class DualWait1 {
public static int value;
static DualWait2 field;

static {
System.out.println("DualWait1.clinit() invoked");
try { Thread.sleep(1000); }
catch (Exception e) { throw new AssertionError(e); }

System.out.println("DualWait1.clinit(): creating a new DualWait2() object");
field = new DualWait2(2);
System.out.println("DualWait1.clinit(): setting value to 1");
value = 1;
System.out.println("DualWait1.clinit(): DualWait2.value="+DualWait2.value);
}

public DualWait1(int id) {
System.out.println("DualWait1: constructor called id["+id+"]");
}
}

// ---- DualWait2.java -----

public class DualWait2 {
public static int value;
static DualWait1 field;

static {
System.out.println("DualWait2.clinit() invoked");
try { Thread.sleep(1000); }
catch (Exception e) { throw new AssertionError(e); }

System.out.println("DualWait2.clinit(): creating a new DualWait1() object");
field = new DualWait1(1);
System.out.println("DualWait2.clinit(): done creating a DualWait1 object");
System.out.println("DualWait2.clinit(): setting value to 1");
value = 1;
System.out.println("DualWait2.clinit(): DualWait1.value="+DualWait1.value);
}

/** Only for a placeholder. */
public static void main(String args[]) {
System.out.println("main(): DualWait1.field = " + DualWait1.field);
System.out.println("main(): DualWait2.field = " + DualWait2.field);
}

public DualWait2(int id) {
System.out.println("DualWait2: constructor called, id["+id+"]");
}

}


Compile the two classfiles separately and once you've done so, you'll get the following output after execution:


DualWait2.clinit() invoked
DualWait2.clinit(): creating a new DualWait1() object
DualWait1.clinit() invoked
DualWait1.clinit(): creating a new DualWait2() object
DualWait2: constructor called, id[2]
DualWait1.clinit(): setting value to 1
DualWait1.clinit(): DualWait2.value=0
DualWait1: constructor called id[1]
DualWait2.clinit(): done creating a DualWait1 object
DualWait2.clinit(): setting value to 1
DualWait2.clinit(): DualWait1.value=1
main(): DualWait1.field = DualWait2@15ff48b
main(): DualWait2.field = DualWait1@affc70


The line in red is where the problem lies. At DualWait1's static constructor, we see that the value for DualWait2's value field is still not initialized, even after a DualWait2 object has already been constructed.

Normally this shouldn't be a problem for normal functioning cases, given that no coder in his right mind would write code like this to trigger the anomaly, so it's more of a curiosity than anything really useful. It's just useful to note, that specifications, while good-intentioned, may sometimes turn out to be impossible to fulfill anyway.
Wednesday, January 23, 2008

Difference between a Register-based and a Stack-based CPU

This is just one small little aspect of how a stack and a register based CPU differs. It is not by any means a good comparison of the intricate differences, but rather just a little trivia that I've remembered in an older CPU era. Back in those days, where people would try to make use of every single CPU cycle that's available, the 'optimal' way of clearing a register on for example, old 8086 machines, a programmer may code their application in C to make use of XOR negation to clear a register. In code, it looks like this:


int a = 8; // just assigning a random value to clear to 0
a = 0; // setting a variable to 0 normally


Versus the 'efficient' way:


int a = 8;
a ^= a; // faster on register CPU, slower on stack CPU


If you translate that to x86 assembly code, it becomes:


MOV AX, 8
MOV AX, 0


Versus:


MOV AX, 8
XOR AX, AX ; same number of instructions, but faster


The arithmetic operation of XOR vs MOV is just faster by 2-3 bus cycles. I might be wrong on the number but the key point is that the savings are really trivial.

Here's a snippet of code generated for a stack based architecture, for example Java:


BIPUSH 8;
ISTORE_0;
ICONST_0;
ISTORE_0; // 4 instructions


Versus:


BIPUSH 8;
ISTORE_0;
ILOAD_0;
ILOAD_0;
IXOR;
ISTORE_0; // 6 instructions!


And you've just added an unnecessary overhead increase of 50%. That's sometimes why people do mumble about stack based machine architectures like Java and C#, which probably have some kernels of truth. But with trivial savings like that, it's probably relevant for old machine architectures, but for modern day RISC machine architectures, the stack slots are mapped onto registers directly, and with compiler optimizations, the differences are probably immaterial.

As a comparison, if the programmer fires up his profiler and optimize on even just the trivial-est of loops, he'll probably end up having a faster application just by doing that, than having to manually tweak every single instance of the given example, with negligible gain (or even a loss of) performance. So if you're ever looking for a good example of an unnecessary and counterproductive 'premature optimization', this is it!
Wednesday, January 16, 2008

Writing your own custom loader for Java

One of the interesting things I've learnt about the Java is that, however much under the illusion that 'java' (or 'java.exe' for windows) is perceived as the JVM itself, the actual fact, is that it is actually not, but rather a very thin front-end for the JVM. The actual code that provides the functioning core of the JVM actually resides in the library (like 'libjvm.so' or 'jvm.dll'), and that the 'java' executable is just a thin veneer on top of the virtual machine.

To demonstrate that this is the case, I'll write a custom loader that invokes the JVM to load a simple Java class file. The code for the simple class file is as follows:


/** Hello world app. */
public class HelloWorld {
public static void main(String args[]) {
System.out.println("Hello World");
}

public static void execute() {
System.out.println("Executed from launcher!");
}
}


The details to the custom loader are documented in Java's Invocation API, which is provided at the end of this article. The code for the loader is down to the bare minimum just for the example to work:


#include <stdlib.h>
#include <stdio.h>
#include <jni.h>

/* This is the program's "main" routine. */
int main (int argc, char *argv[]) {

JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args;
JavaVMOption options[1];

jint res;
jclass cls;
jmethodID mid;

/* IMPORTANT: need to specify vm_args version especially if you are not using JDK1.1.
* Otherwise, will the compiler will revert to using the 'JDK1_1InitArgs' struct.
*/
vm_args.version = JNI_VERSION_1_4;

/* This option doesn't do anything, just to illustrate how to pass args to JVM. */
options[0].optionString = "-verbose:none";
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_FALSE;

/* load and initialize a Java VM, return a JNI interface pointer in env */
res = JNI_CreateJavaVM(&jvm,(void**)&env,&vm_args);
if (res < 0) {
fprintf(stderr, "Can't create Java VM\n");
exit(1);
}

jclass ver;
jmethodID print;

ver = (*env)->FindClass(env, "sun/misc/Version");
if (ver == 0) {
fprintf(stderr, "Can't find Version");
}
print = (*env)->GetStaticMethodID(env, ver, "print", "()V");
(*env)->CallStaticVoidMethod(env, ver, print);

/* invoke the Main.test method using the JNI */
cls = (*env)->FindClass(env, "HelloWorld");
if (cls == 0) {
fprintf(stderr, "Can't find HelloWorld.class\n");
exit(1);
}

mid = (*env)->GetStaticMethodID(env, cls, "execute", "()V");
if (mid==0) {
fprintf(stderr, "No such method!\n");
exit(1);
}
// otherwise execute this method
(*env)->CallStaticVoidMethod(env, cls, mid);

/* We are done. */
(*jvm)->DestroyJavaVM(jvm);

return 0;
}


What remains is just compiling and executing it. I'm very rusty on using 'make', and have really little experience with any of the gnu build tools (auto{make,conf} and family), but since the compilation is rather straightforward, you can pass it something like:


gcc -g -Wall -I/opt/jdk1.6.0_02/include/ -I/opt/jdk1.6.0_02/include/linux/ -L./jre/lib/i386/client/ -ljvm -o invoker invoker.c


Just change /opt/jdk1.6.0_02/include/{,linux} to where ever your java header files reside. One of the funny things I've found with gcc, was that no matter how I force the linker to link it with the java library I've provided, it always seems to link with the original libraries that came installed on my computer. So at the first execution the output comes out like this:


% invoker
java version "1.4.2-03"
Java(TM) 2 Runtime Environment, Standard Edition (build Blackdown-1.4.2-03)
Java HotSpot(TM) Server VM (build Blackdown-1.4.2-03, mixed mode)
Can't find HelloWorld.class


Not only it could not find HelloWorld.class, which simply resides in the same directory as 'invoker', it's telling me that it's running the original Blackdown 1.4 JVM that came default on my linux distro as well! Unfortunately that's not what I wanted. The way to remedy that is either to dynamically link to 'libjvm.so' (see [1] for details) or just cheat by modifying the LD_LIBRARY_PATH variable in linux:


% LD_LIBRARY_PATH=./jre/lib/i386/client/ ./invoker
java version "1.5.0_03"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_03-b07)
Java HotSpot(TM) Client VM (build 1.5.0_03-b07, mixed mode)
Executed from launcher!


By mangling LD_LIBRARY_PATH, I've just swapped out the JVM without even recompiling my invoker application, which surprised me just how trivial the 'java' executable actually is, even that's what everybody uses all the time.

Links
[1] The Invocation Interface: http://java.sun.com/docs/books/jni/html/invoke.html
[2] The Invocation API: http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/invocation.html
Sunday, January 13, 2008

Bundling a minimal 'bare bones' JVM with your Application

One of the gripes why people dislike Java as a desktop application is probably because of the additional requisite of having the presence of the JVM, which is generally either not bundled with any of the major operating systems in the market today, or that it can be the wrong version of the JVM that is needed. This makes it really annoying for end users if they just wanted something that 'works out of the box'.

I got interested with the idea that, what if your application can create a seamless experience, as if it's like a binary application running on the native platform, by having the JVM embedded in as part of the package, so that most of the core application besides the loader can be written in Java?

The impetus for this is because that the entire JVM is usually a huge piece of bloat, and mostly, you don't really need to use every single feature that is present. So what about just packing only the actual components that really have to present for an application to work?

With respect to a simple application that prints out just 'Hello World', just how many files are there that the JVM really needs in order to function? So that's when I started experimenting by taking my Java installation and picking out each individual file apart, leaving only the files that would otherwise cause the JVM to fail. By trial and error, here's what I found to be required in order to have the minimal JVM functioning:

70k     jre/bin/java
103k    jre/bin
29k     jre/lib/i386/native_threads/libhpi.so
33k     jre/lib/i386/native_threads
4.3M    jre/lib/i386/client/libjvm.so
4.4M    jre/lib/i386/client
91k     jre/lib/i386/libzip.so
50k     jre/lib/i386/libverify.so
148k    jre/lib/i386/libjava.so
4.1k    jre/lib/i386/jvm.cfg
4.7M    jre/lib/i386
467k    jre/lib/rt.jar*


What you're seeing here is the output from a linux version of the JVM, hence the point to note from this is, that the file prefix and what follows after will be different for different operating systems, although the names should largely remain the same. What I mean by that is, that for example in Windows, 'java' will be 'java.exe', and for libraries like libjvm.so' will be corresponding to 'jvm.dll' instead.

The sum of the size of the files are roughly a little past 5Mb. But in order to get to this figure, there are a number of additional steps that I have to take, so in some ways this isn't actually a fully functioning JVM, for various reasons:

1) I've left out most of the 'core' dynamic libraries, like awt, sound, network, io, nio, awt and various libraries that resides in the /lib/i386/ directory. If the application attempts to utilize those corresponding java classes, the JVM will fail given the underlying libraries which provides the actual implementation aren't present.

2) Also because that actual binary libraries aren't around, there really isn't any need in having those corresponding class files as well, so I've taken it out from the core rt.jar jar package, which is where the Java system classes reside. The original rt.jar is probably 20Mb worth, so in this case, it is worth the trouble in doing that.

Caveat is, this is an error-prone process, and the files used by the JVM is guesstimated by passing the java launcher with the '-verbose:class' flag and capturing the resulting classes loaded, and subsequently removing all the remaining untouched files from 'rt.jar'. The list of classes loaded looks something like this:

% java -class:verbose HelloWorld
[Opened /opt/sun-jdk-1.5.0.08/jre/lib/rt.jar]
[Opened /opt/sun-jdk-1.5.0.08/jre/lib/jsse.jar]
[Opened /opt/sun-jdk-1.5.0.08/jre/lib/jce.jar]
[Opened /opt/sun-jdk-1.5.0.08/jre/lib/charsets.jar]
[Loaded java.lang.Object from /opt/sun-jdk-1.5.0.08/jre/lib/rt.jar]
[Loaded java.io.Serializable from /opt/sun-jdk-1.5.0.08/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /opt/sun-jdk-1.5.0.08/jre/lib/rt.jar]

  ... [ lines truncated for brevity ] ...

[Loaded java.security.Principal from /opt/sun-jdk-1.5.0.08/jre/lib/rt.jar]
[Loaded java.security.cert.Certificate from /opt/sun-jdk-1.5.0.08/jre/lib/rt.jar]
[Loaded HelloWorld from file:/home/vincent/code/]
Hello World
[Loaded java.lang.Shutdown from /opt/sun-jdk-1.5.0.08/jre/lib/rt.jar]
[Loaded java.lang.Shutdown$Lock from /opt/sun-jdk-1.5.0.08/jre/lib/rt.jar]


Many people may be surprised that for a simple 'HelloWorld' application needs to load so many class files prior to execution. Because of that, and the series of auxiliary operations that the JVM performs before the application is start, this accounts for the 'slow start' phenomenon that we normally encounter with Java apps. In order to use this information, you'll probably need to pipe the result into a file, and perform some manipulation before you can pass the list to 'jar' for repackaging.

The error proneness that I mentioned about, lies in the fact that applications that can dynamically load other classes at any given point in time, which means you can have missing class files that are required which is not captured by the profile of a single instance of execution. This shouldn't be a problem as a developer, since it should be a relatively straightforward exercise to find out what system packages your application use.

After all that trimming, the entire embedded system adds up to about 5Mb worth, which is a sensible size for embedding into your application. But when compared to the offline java installer, which only comes about 20Mb compressed, the savings isn't really that substantial, given that downloads are getting cheaper and faster by the day. I'm sure your YouTube bandwidth use will easily have exceeded that in any given day, so it might just be as well that not worth the effort to have the JVM integrated into your application, and instead have an installation script that will seamlessly install the JVM to work with it.
Thursday, July 26, 2007

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.