It's been over two years since I wrote my last article about using the Java runtime on Linux ("Java Technology on the Linux Platform" [JDJ, Vol. 5, issue 12]). The Java platform and Linux distributions have not stood still during that time, so I'm taking this opportunity to answer some of the frequent questions that have surfaced since then and provide some insight into some of the more complex issues. If you're a seasoned Java on Linux user or are planning to move to the Linux platform, I trust you'll find the answer you're looking for!
One well-known difference between deploying on Linux compared to other Unix operating systems is the implementation of the system threads library. Linux threads are implemented as a cloned process and, consequently, the underlying thread implementation can affect the behavior of the Java runtime.
The most visible difference when deploying a Java application on Linux is that each Java thread shows up as its own process. This means that the /proc filesystem will have an entry for each thread and the output of the ps command will list 10 or so processes for a single Java Virtual Machine. This is normal, but it's also confusing and inefficient. Later releases of the ps command (in package procps 2.0.7 and later) will hide the Java threads and display only the master process in the ps output. The full list of threads can be displayed using -m option to ps. Only in newer releases of the Linux kernel will the number of entries in the /proc filesystem be reduced.
The overhead for implementing threads using this design means that you may need to allow a little more time for Java applications to start up on Linux and reduce the number of concurrent threads if the application is thread heavy.
Why Doesn't Someone Fix This?
The scalability and signal-handling issues of the LinuxThreads implementation are well known, and over the past two years there have been several attempts to improve the situation. The two most well-known thread library projects are Next Generation Posix Threads (NGPT) and a newer library called Native Posix Thread Library (NPTL).
The earlier NGPT project is based on an M:N thread mapping that has been popular with other Unix operating systems. The M:N solution maps multiple user threads, Java threads in our case, and often runs those threads on a smaller number of kernel threads. Kernel threads are traditionally seen as more expensive for an operating system than user threads.
The newer NPTL approach is to keep the 1:1 thread mapping - one user or Java thread to one kernel thread - but optimize the kernel for thread-related operations, including signal handling, synchronization, and thread creation speed.
Both thread libraries also require modifications to the Linux kernel. NPTL requires the 2.5 kernel that's the development version of the official 2.6 Linux kernel. NGPT can be bolted on to existing 2.4 kernel distributions by rebuilding the kernel with an NGPT patch.
There's also a chance that some of the necessary NPTL kernel changes may also be back ported to 2.4 kernels as a patch.
We've been evaluating both these libraries and have seen some encouraging results from the newer NPTL library due to its compatibility with the existing LinuxThreads pthread library. The current level of compatibility means that applications like the Java runtime will not need to be recompiled or ported and will work "out of the box."
Do You Really Need All Those Threads?
The new thread libraries may still take another year before they're adopted by the major Linux distributions. In the meantime, you can make a real difference now to server applications that use many sockets. Before J2SE 1.4, most multithreaded Java server applications used blocking IO to wait for requests from the client. Blocking IO in this scenario resulted in a single thread for every server socket connection.
You can reduce the number of threads by using either a connection pool of listening threads or the new New IO Selector and ServerSocketChannel. The Selector class available in J2SE 1.4 manages a list of registered channels and returns those with an event pending. Each ServerSocketChannel can be placed in a nonblocking mode; in this mode the Selector can be used to multiplex sockets in a way that's similar to the Unix poll command. The scalability benefits of using one thread to manage socket connections can be seen in Figure 1.
The chat server used in this test was first implemented using blocking IO (Old IO), and then rewritten to use the multiplexing sockets (New IO). The Linux thread overhead becomes apparent after as little as 100 connections.
The Java Runtime and GCC 3.2
Over the past year there have been significant changes to the GNU C++ Application Binary Interface (ABI). This means that programs built against an earlier C++ library are not compatible with newer C++ libraries and vice versa.
This change in compatibility is apparent if you try to use a Java plug-in with a build of the Mozilla Web browser that was compiled with GCC 3.2. Fortunately, most Linux distributions still supply a browser based on the earlier C++ interface. We're working with the Mozilla team to remove the C++ plug-in dependency; this would enable the plug-in to work with a browser built with either compiler. If the Java runtime were built with GCC 3.2 today, then the plug-in wouldn't work on an earlier browser.
We are planning to build the next major J2SE release on Linux with GCC 3.2, and it should work on both old and new browsers. Our friends at blackdown.org are also planning to release a special GCC 3.2 version for testing.
Java Plug-in Installation Tips for Mozilla
If you've just downloaded the latest Mozilla browser (1.2) and then loaded a page with a Java applet, you'll be prompted to download the Java plug-in. However, the latest plug-in available on the download server is only JRE 1.3.1. To use a later plug-in, such as 1.4.1_01, you need to do the following:
1. Download JRE 1.4.1_01 or J2SDK 1.4.1_01 from http://java.sun.com.
2. Change directory (cd) to the Mozilla installation directory. The default installation directory is /usr/local/mozilla.
3. Find the plugins directory.
4. Remove the libjavaplugin_oji.so file from the plug-in directory, if it exists.
5. Link the libjavaplugin_oji.so file from the JRE or JDK you downloaded to the plug-in directory. The libjavaplugin_oji.so file is either in the directory jre/plugin/i386/ns610 or plugin/i386/ns610, depending on whether you installed the JDK or JRE, respectively
The following small script can be used if you have installed JDK 1.4.1_01 and are using the default Mozilla install directory.
To verify that you have successfully upgraded your plug-in, look at the Java console output. This can be configured using the ControlPanel command from the Java runtime.
Linux Debugging Tools
Whenever possible try to use a Java IDE to debug Java programs. The Linux debug and tracing tools do work but are not as advanced as other operating systems. The most useful tools are ltrace, used to trace library calls; strace to trace system calls, especially useful for catching files that can't be opened or permission problems; and finally the GNU debug tool GDB. The ltrace and strace tools are available on most distributions, but are not always installed by default.
Using GDB on a Running Process
As Linux threads are implemented as Linux processes, this makes debugging Java programs running on Linux a little more complicated. If the JVM is already running, you can attach the system GDB tool to the JVM as follows:
gdb /usr/java/j2sdk1.4.1_01/bin/java 11712
The 11712 number in the example is the parent Java process thread ID. Most of the Java processes in a ps listing will have a common parent process; this is the parent thread that you'll use as the process number to pass to the GDB.
With the GDB attached to the JVM, you can run any GDB commands. If you've used GDB before, these commands will look familiar. The info threads command lists the Linux threads, the t command selects a thread to be the current thread, and the where command lists the stack frames in that thread.
Starting a Java Virtual Machine Using GDB
Attaching a GDB trace to an existing program is normally the simplest way to debug a deadlocked JVM; however, you might want to start the JVM from within the GDB debug tool, especially if you're trying to debug some native code from a JNI library.
To enable the JVM to run from inside GDB on Linux with J2SE 1.4.0 and later, use the following steps.
First, set the LD_LIBRARY_PATH environment variable to point to the Java installation directories; the example shown is for the J2SE 1.4.1_01 release. You can add your own LD_LIBRARY_PATH to point to a JNI library, for example, but add those directories to the end of the LD_LIBRARY_PATH variable.
Next start GDB. From J2SE 1.4.0 onward the Java shell script wrapper used to start the JVM has been replaced by a small executable program. The GDB session can simply be started as follows:
All that remains is to instruct GDB to ignore some of the common signals used by the Hotspot Virtual Machine, and then provide the arguments to the Java program, in this instance the Java2Demo.jar file.
handle SIGUSR1 nostop noprint pass
handle SIGUSR2 nostop noprint pass
run -jar Java2Demo.jar
If you want to stop in your own JNI library, enter the following GDB command to trap each time a dynamic library is loaded.
set stop-on-solib-events 1
Use the cont GDB command to continue execution until you can see that your library has been loaded; this should occur after a dozen attempts. You can list the shared libraries by running the sharedlibrary GDB command.
Finally, to break on a function in your JNI library, enter the GDB command break with the function name you want to stop in.
Using Linux to develop and deploy applications written in the Java programming language has become increasingly popular as the Linux platform has reached new heights. With the introduction of the new threads libraries and the transition to the new C++ ABI complete, the next year promises to be an exciting one for Java and Linux users.
ResourcesNative POSIX Thread Library:
Next Generation POSIX Threading:
Java technologies on Linux: http://java.sun.com/linux
Blackdown Java porting team:
Calvin Austin is the J2SE 1.5 specification lead and lead engineer for the Java 2 Standard Edition Linux project at Sun Microsystems, Inc., and works with the Blackdown.org Java porting group. He is cofounder of the Java Developer Connection, and also the coauthor of Advanced Programming for the Java 2 Platform