3

I was investigating what system calls are made on a simple HelloWorld java program. With a simple strace I noticed that there is no write call, which I found suspicious:

...
mprotect(0x7f0bcd852000, 4096, PROT_READ) = 0
mprotect(0x7f0bce915000, 790528, PROT_READ) = 0
getpid()                                = 27931
munmap(0x7f0bcf6ac000, 174284)          = 0
getpid()                                = 27931
mmap(NULL, 1052672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f0bcf5d6000
clone(child_stack=0x7f0bcf6d5fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f0bcf6d69d0, tls=0x7f0bcf6d6700, child_tidptr=0x7f0bcf6d69d0) = 27932
futex(0x7f0bcf6d69d0, FUTEX_WAIT, 27932, NULLHello, World
) = 0
munmap(0x7f0bbfa6c000, 140063364)       = 0
close(3)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

So, as you can see above, with a normal strace call only a futex call is made, which is waiting on the string address.

So, I ran strace with the -f parameter to see all the threads:

[pid 28249] pread64(3, "\312\376\272\276\0\0\0009\0\330\n\0\2\0\3\7\0\4\f\0\5\0\6\1\0\23java/n"..., 6104, 4355323) = 6104
[pid 28249] pread64(3, "\312\376\272\276\0\0\0009\0\306\n\0\2\0\3\7\0\4\f\0\5\0\6\1\0\20java/l"..., 4455, 3263638) = 4455
[pid 28249] write(1, "Hello, World\n", 13Hello, World
) = 13
[pid 28249] pread64(3, "\312\376\272\276\0\0\0009\0(\n\0\2\0\3\7\0\4\f\0\5\0\6\1\0\25java/l"..., 999, 4425942) = 999
[pid 28249] mmap(0x7f9d35ae2000, 16384, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7f9d35ae2000
[pid 28249] mprotect(0x7f9d2c2e7000, 8192, PROT_READ|PROT_WRITE) = 0

Like this, I can see the write call as well as much more futex calls.

My question is, what exactly happens at the threading level when System.out.println is called? Does Java create a dedicated thread for printing, that is then synchronized with the main thread? Also, what do they use for synchronization that in the end makes a futex call.

danield
  • 195
  • 1
  • 7
  • This is not an answer, but it might be interesting: https://luckytoilet.wordpress.com/2010/05/21/how-system-out-println-really-works/ – Peter Bagyinszki Aug 30 '21 at 09:25
  • *"which is waiting on the string address"* - Not exactly. What you see in the output is that "Hello world" is printed from another thread while the primordial thread is waiting on a futex (completely unrelated to the string). – apangin Aug 30 '21 at 09:42
  • "What exactly happens at the threading level when `System.out.println()` is called?" Synchronization, "Does Java create a dedicated thread for printing, that is then synchronized with the main thread?" No. – user207421 Aug 30 '21 at 10:16

2 Answers2

8

System.out.println has nothing to do with threads (except that PrintStream methods are synchronized, but that doesn't matter in an uncontended case).

Java launcher creates a JVM in a new thread, that's why any Java code (not just println) is executed in a non-primordial thread.

Running Java code in a primordial thread used to cause many problems, see JDK-6316197 for details.

apangin
  • 92,924
  • 10
  • 193
  • 247
  • Ah ok, so when you write `java HelloWorld` for example, the java JVM gets the program that will be executed, does some initialization (I guess) and then launches the thread with our actual program. And if we don't manually create some other threads in the program then it will all get executed in that thread. Is this correct? – danield Aug 30 '21 at 10:15
  • @danield No, it's [launcher](https://stackoverflow.com/a/26025656/3448419) that creates a new thread even before initializing the JVM. JVM then performs initialization and runs the application in that new thread. – apangin Aug 30 '21 at 11:39
0

Java's System.out is a PrintStream and one of its methods is:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

There you can see the synchronization (as there is in all the other print methods in that class).

This is used so that we will not get garbled text if multiple threads write to System.out (or any other PrintStream for that matter). At least it gets easier to control on full-string parameters, as opposed to writing single chars. Which is also possible but might cause garbled output.

I do not know about the rest, the inner workings of how the OS dependent I/O is handled, there's probably more going on. But I did not take a look at that.

JayC667
  • 2,418
  • 2
  • 17
  • 31