Where does System.out come from?

Finally, an interesting question! Where does System.out come from? In the aforementioned thread, I have posted an explanation that simplified what was happening behind the scene; but very much in line with Don Knuth spirit, I gave you a white lie: this is not entirely the whole truth. Though the general idea is still the same. I’ll shamelessly copy-paste some of my own response, because yes, I’m that lazy.

(All the explanation below is based on the OpenJDK 6 sources, so it might be slightly different in other VMs)

When you first look at the code in the System class, you get quite confused:

   // ...
    public final static InputStream in = nullInputStream();
    // ...
    public final static PrintStream out = nullPrintStream();
    // ...
    public final static PrintStream err = nullPrintStream();

    // ...
    private static PrintStream nullPrintStream() throws NullPointerException {
	if (currentTimeMillis() > 0) {
	    return null;
	}
	throw new NullPointerException();
    }

So it either returns null, or a NullPointerException?? First, let’s have at currentTimeMillis(), which is part of the condition:

   public static native long currentTimeMillis();

currentTimeMillis is a native method. Native methods are methods implemented in another language (usually C or C++). This method is mapped to its C counterpart thanks to the following JNI mechanism:

public final class System {
    private static native void registerNatives();
    static {
        registerNatives();
    }

    // ...

registerNatives is defined in what we call a static block, which is a block of code called only once, at classloading time. Static blocks are used for example in JDBC when you do Class.forName("my.nice.Driver"): the driver class has a static block with which it registers itself with the DriverManager, and allows you to then use the DriverManager for, say, giving you a connection. So here, the static block calls a native method, and this native method executes the C code below (Open JDK) in System.c:

static JNINativeMethod methods[] = {
    {"currentTimeMillis", "()J",              (void *)&JVM_CurrentTimeMillis},
    {"nanoTime",          "()J",              (void *)&JVM_NanoTime},
    {"arraycopy",     "(" OBJ "I" OBJ "II)V", (void *)&JVM_ArrayCopy},
};

#undef OBJ

JNIEXPORT void JNICALL
Java_java_lang_System_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

So I kind of lied when I said in my post that the streams were initialized by registerNatives. Well, not entirely, you’ll see why.

So when initializing the class variables out, in and err, currentTimeMillis should be defined, and these final variables are then set to null. If you get a NullPointerException, something utterly wrong has happened, and the JVM will probably shut down.

System gets initialized at a very specific point in the whole vm startup: it is loaded when the main thread is being kickstarted. In a file called thread.cpp, the following piece of code is executed:

  // Initialize java_lang.System (needed before creating the thread)
    if (InitializeJavaLangSystem) {
      initialize_class(vmSymbolHandles::java_lang_System(), CHECK_0);

      // ...

      call_initializeSystemClass(CHECK_0);

And there you go: the System class gets loaded, and the static block is executed. We now have access to currentTimeMillis, and streams out, err and in will be null. Further down, the call_initializeSystemClass(CHECK_0) function is called, and that’s the function that actually calls the private method initializeSystemClass that calls the native methods setIn0, setOut0 and setErr0 which do initialize the streams properly.

Why are these setters (setIn0, setErr0 and setOut0) native? It’s because they are initializing final variables a second time, since they had already been set to null. Making them native allows the VM to bypass the language restriction to completely initialize the streams.

Once the VM is loaded, and again if nothing dramatic has happened, then System is available for business, along with its streams! This concludes this post, but hopefully this should the first post of new series I’m thinking of writing, called “The JVM in depth”.

 
---

Comment

your_ip_is_blacklisted_by sbl.spamhaus.org

---