Friday, May 8, 2015

Which Java am I using?

What happens if you have multiple JREs on a computer? Which one is is used when you run java at the command line?

You might tell me to look at the JAVA_HOME environment variable, but the only Java launcher I know of that actually checks it is the one that gets installed on OS X. There is no joy with JAVA_HOME for Windows or Linux.

Sometimes I need to know which JRE is being used. Maybe an application has a specific requirement, or I want to change the configuration perhaps to edit the java.policy and allow the Apache Derby database to bind to a TCP/IP port if I have Java 7 or higher.

The Quick Answers

On all platforms you can control which java executable is picked by putting the bin folder of a JRE earlier in the environment PATH variable and bypassing the launcher. Many folks like to set the environment variable JAVA_HOME to point to the the JRE (or JDK) folder, and then use that variable in the path: $JAVA_HOME/bin on OS X and Linux, %JAVA_HOME%\bin on Windows. When you set the variable then changes are reflected in the PATH (if you open a new terminal or command window) and it is always available in any scripts that use it.

If you need a particular JRE for an application, then wrap the application in a startup script that hardwires which JRE will be used to launch it. Other applications that need the default JRE will not be affected.

So the fast answer is that running the java command usually launches java in the last JRE that was installed, unless someone changed that configuration. But what if you don't know what was installed last? Generally you can look at the version and update numbers, but sometimes there may be multiple JREs of the same version.

The fastest way to solve that problem is to execute java -verbose -version. It will not announce the path for you, but it does log to the console all of the jar files loaded. The path to those jar files tells you where the JRE is!

So the rest of this post is an exploration of how the Java launcher actually goes about selecting the JRE that gets used. It is fun, it can be useful to understand what is going to happen when you type java, and it is mostly here so I can refer back to it. It turns out that the decision making process is not the same on every platform. Shocker.

The Java Launcher

OS X and Microsoft Windows use Java launcher applications that wraps the Java virtual machine program (JVM). So the launcher is what normally runs when you run java on the command line or in a script. The launchers are designed to figure out which JRE should actually be used. They have their own options that help them with that decision, options that are not part of the actual JVM.

First off, if you set the environment variable _JAVA_LAUNCHER_DEBUG=1 the launchers will all print out trace information. That trace includes a declaration of the JRE that will actually be used. It also includes the FULL_VERSION, which is the complete version number with update and build values, and the DOT_VERSION which is only the major and minor version numbers (like 1.8).

The launcher also has a -version:<value> option which tells the launcher to find an launch Java from a particular JRE. The value can be anything from the dot version through the full version.

The JVM Architecture

A question that often comes up is how to show the architecture: 32bit or x86, 64bit or x64. Usually searchers are pointed towards code that will get and display the property in the JVM:

public static void main(String[] args) {

@tab;System.out.println(System.getProperty("os.arch"));
}

That is great if you really need to know the architecture in a program, but a simpler solution at the command line is to examine the output of java -verbose -version: the fourth line from the end will read something like one of these two examples. 32bit versions are not labeled, but 64bit versions are:

Java Hotspot (TM) Client VM (build 24.75-b04, mixed mode, sharing)

Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

Microsoft Windows

The 64bit versions of both the JDKs and JREs are installed in C:\Program Files\Java, and the 32bit versions in C:\Program Files x86\Java (assuming that C is your root drive of course). The windows installation allows you to keep older versions JDKs and JREs around, but to use multiple JDKs and JREs they must be installed in order. The installation software will not allow you to install an earlier version of Java than the last one that it finds on the system. You can uninstall the later versions, install the earlier versions, and the later ones after that. And sometimes you will have both 64bit and 32bit versions installed for different applications; for example the 64bit version of Eclipse-based IDEs need the 64bit version of Java.

Up until Java 8 update 11 the Java launcher is found at %WINDIR%\System32\java.exe, where %WINDIR% usually evaluates to C:\Windows. It gets updated when you install a new JDK or JRE. In update 11 it was moved to C:\ProgramData\Oracle\Java\Javapath\java.exe and the directory is added to the beginning of your PATH.

Starting with the Java 8 update 25 JDK, but only with the JDK, %WINDIR%\System32\java.exe is removed by the installation. The JRE installation leaves it behind.

The -version:<value> option in the Windows launcher is limited to other versions in the same family: If the launcher is a 1.8 launcher it can only launch different 1.8 updates.

The Windows Java launcher ignores the environment variable JAVA_HOME, at least in any version of the source code that I have examined. You can watch what the launcher does by setting the environment variable _JAVA_LAUNCHER_DEBUG=1. The launcher's decision making process follows these steps to look for the JRE in the registry:
  1. Open the registry path "HKLM\Software\JavaSoft\Java Runtime Environment".
  2. Look in the CurrentVersion key (a string) for the current Java version.
  3. The current version MUST MATCH the version hardwired into the Java launcher or it will fail and print an error. If the Launcher expects 1.8, then the current version must be 1.8. You cannot edit the registry and change a Java 1.8 launcher to launch 1.7. It is a security feature.
  4. Append the version to the registry path, that will look like "HKLM\Software\JavaSoft\Java Runtime Environment\1.8."
  5. in that folder look for the key JavaHome, it points to the correct JRE. It should point to the last update of the current version installed, like "C:\Program Files\Java\jre1.8_40."
  6. This is the version of the JRE that will get launched. The launcher ignores the "RuntimeLib" key completely. If you change the JavaHome key to point to an earlier version, that will work! But we do not recommend it.

OS X

The JDKs install to the system-wide folder /Library/Java/JavaVirtualMachines. Their structure is a little different than the other platforms because the follow the OS X package format. In each one you have to drill down to the Contents/Home folder.

The JREs install to /System/Library/JavaVM.Framework/Versions. Historically older JREs were left available here, and you will probably see Java 1.4 through Java 6. Now when an OS X JRE is installed all it is used for is the browser plugin, and the installation always replaces whatever the current JRE is in the folder /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/. The java launcher never uses the JRE installation, only the JDK installations.

The Java launcher is normally found at /usr/bin/java. This is a symbolic link to /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java. The Current folder is just a symbolic link to the A folder. The A folder contains the Commands folder that has java launcher programs in it: java, javac, etc. This java launcher does in fact check the JAVA_HOME environment variable and if it finds it that is the JRE it selects. Otherwise it picks the JRE in the most recent JDK install to use..

Linux

Linux systems are the real odd-balls. Depending on the version of Linux and the specific vendor platform installed, and how Java was installed, then where Java is could be in vastly different places. Usually there is a soft link /usr/bin/java, which may point to other soft links, but ultimately ends up at a real file.

I do not have time to go through Ubuntu, Suse and all the other versions here so I am just going to focus on CentOS 7, which will have the same layout as Red Hat. Here /usr/bin/java 

On some systems the file may be a specific java in a JRE. On my Centos 7 system it actually points to a shell script that decides which java executable to launch based on the existence of a shared library, the point being to use the faster loading version with the shared library if it is available. The absolute paths to the JRE it uses are hardwired into that script.

The End

I need to thank my friend Jason Gibson for driving me to madness, er, I mean to figuring out and document how everything really works! Mostly we keep using Derby in classes where students have multiple JREs installed and identifying which one to configure is always the problem.

I went down rabbit-hole after rabbit-hole. As a result I figured out out to find the default JRE without a lot of the work we were doing before. And I built a java policy file to launch Apache Derby without reconfiguring the JRE: Apache Derby Won't Start Anymore!

No comments:

Post a Comment