Thursday, September 3, 2009

Fixing Java on Mac Snow Leopard

I have long documented some of the issues and differences for a Java developer on the Mac OS X. Most of that has been on Leopard, OS X 10.5. For quick links the most significant articles are: Java is a 2nd Class Citizen on MAC OSX, Fixing Java Memory Tools on Mac OS X and Java 7 on Mac OS X.

Monday I was faced with a new situation, Snow Leopard OS X 10.6. There are several issues with Java on the new Mac OSX 10.6 "Snow Leopard". This post will outline the known issues and will provide fixes for several of them (or provide links where fixes are already explained).

Known Issues:
  1. Java 5 is not installed
  2. Java 5 symbolic links are set to Java 6
  3. jmap - some aspects of jmap do not function
  4. javascript runtime is still missing
As a consequence, applications that used Java 5 either don't work, or provide a message as shown here from my favorite stock trading application.



* I'm still looking for more issues, so if you can add to the list, please add comments, email or twitter.
** I have tested the latest BTrace and it works fine.


Java 5 on Snow Leopard
Whither you agree with the removal of Java 5 or not, it is unbelievable to me that the Java 5 symbolic links are pointing to the CurrentJDK which points to 1.6. For those new to this space the Java version live under /System/Library/Frameworks/JavaVM.framework/Versions . I would advise you to either:
  1. replace Java 5 using instructions provided at leopard http://wiki.oneswarm.org/index.php/OS_X_10.6_Snow_Leopard
  2. simply delete the java 5 symbolic links
As a side note, I use a script for swapping between JVM versions. I modified the original script to just include 3 important functions when sourced in. I'll add the script to the bottom of this post as a reference. The 3 functions are jvms, setJava, unsetJava.
  • jvms reads through the version directory and reports what it reads of the file system.
  • setJava allows you to type in the name of the version and switches JAVA_HOME and path to this version of the JVM.
  • unsetJava reverts the set to the original java version
JMap on Snow Leopard
My initial execution of jmap on snow leopard failed with the following error message:

kensipe$ jmap 3407
Attaching to process ID 3407, please wait...
sun.jvm.hotspot.debugger.NoSuchSymbolException: Could not find symbol "heapOopSize" in any of the known library names (-)
at sun.jvm.hotspot.HotSpotTypeDataBase.lookupInProcess(HotSpotTypeDataBase.java:399)
at sun.jvm.hotspot.HotSpotTypeDataBase.readVMIntConstants(HotSpotTypeDataBase.java:319)
at sun.jvm.hotspot.HotSpotTypeDataBase.(HotSpotTypeDataBase.java:88)
at sun.jvm.hotspot.MacOSXTypeDataBase.(MacOSXTypeDataBase.java:36)
at sun.jvm.hotspot.bugspot.BugSpotAgent.setupVM(BugSpotAgent.java:578)
at sun.jvm.hotspot.bugspot.BugSpotAgent.go(BugSpotAgent.java:499)
at sun.jvm.hotspot.bugspot.BugSpotAgent.attach(BugSpotAgent.java:337)
at sun.jvm.hotspot.tools.Tool.start(Tool.java:163)
at sun.jvm.hotspot.tools.PMap.main(PMap.java:67)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at sun.tools.jmap.JMap.runTool(JMap.java:179)
at sun.tools.jmap.JMap.main(JMap.java:110)
Debugger attached successfully.

The good news is that the debugger attached successfully :)

Information regarding this issue is scarce. It turns out to likely be an issue with the version of Java that is distributed with snow leopard. The JDK 1.6 rev 14 appears (based on non-confirmed web sources) to have this issue. The latest is JDK 1.6 rev 16 should have the fix, however using the apple distro, we are left waiting for Apple for the fix. Although jmap fails with no arguments it does succeed for certain commands such as jmap -histo 3407.
After replacing the JDK 5 with the leopard distro as advertised above and switching to this version with the script above, jmap works fine. However jdk 5 jmap only works against jdk 5 running applications. It fails against java 6.

javascript
As record in a posting from a year ago, called Fixing Java Memory Tools on Mac OS X, the javascript library is still not provided by Apple as part of the Java distro. You will need to following the instructions there to fix it. Unfortunately the Rhino download has disappeared from the Mozilla site, so if you didn't keep a copy, ping me and I will send you want I have. You will also need to duplicate this for each version of Java if you install the Java 5 Leopard package.
The test to see if this is setup correctly for you is: jrunscript -q
If that command only returns AppleScript, then you are missing the javascript engine which is need for a number of tools in the JDK.

After these changes, it appears that Snow Leopard is ready for some hard core Java development. Good Luck and Happy Coding!

Java Switching Script that I use:



# *************************************************************************
# Originally written by Shawn Erickson - shawn at freetimesw.com
#
#
# Last Update: 2008y 08m 8d
#
# The following functions are meant for use with bash shell which is currently the
# default on Mac OS X 10.4 (starting with 10.3 IIRC) unless otherwise configured.
# Good info on why not sym links: http://lists.apple.com/archives/Java-dev/2006/Jan/msg00290.html
#
# from: http://lists.apple.com/archives/Java-dev/2005/Aug/msg00506.html
# http://homepage.mac.com/shawnce/misc/java_functions_bashrc.txt
#
# *************************************************************************

### Prompt ###

export CURRENT_MODE_STRING="";

### Java Environment Functions ###

J_VERSIONS_DIRECTORY="/System/Library/Frameworks/JavaVM.framework/Versions"
J_COMMANDS_SUBPATH="Commands"
J_HOME_SUBPATH="Home"

function availableJVMs()
{
ls -1 $J_VERSIONS_DIRECTORY | grep ^[0-9].[0-9]
}

function jvms()
{
clear
echo "Available JVMs: "$jvms
ls -1 $J_VERSIONS_DIRECTORY | grep ^[0-9].[0-9] # look to remove redundant call
echo " "

echo "Current Java:"
java -version
}

function setJava()
{
local target_jvm=""
local jvms=$(availableJVMs)

# Validate that the user requested an available JVM present on the system

for jvm in $jvms ; do
if [ "$jvm" == "$@" ]; then
target_jvm=$@
fi
done

if [ "$target_jvm" == "" ]; then
echo "Unsupported Java version requested"
return;
fi

# If we get here the user asked for a valid JVM, so configure it

echo "Configuring Shell Environment for Java "$@
export JAVA_VERSION=$@

# First unset any current set java, back to default before doing configuration
_unsetJava

# Generate the paths needed for the JVM requested
local jcmd="${J_VERSIONS_DIRECTORY}/$@/${J_COMMANDS_SUBPATH}"
local jhome="${J_VERSIONS_DIRECTORY}/$@/${J_HOME_SUBPATH}"

# We save the original path so we can toggle back if unset
ORIGINAL_PATH="$PATH"
PATH="$jcmd:${PATH}"

# We save the original JAVA_HOME so we can toggle back if unset
ORIGINAL_JAVA_HOME="$JAVA_HOME"
JAVA_HOME="$jhome"

# Update command prompt mode tag to note JVM setting
CURRENT_MODE_STRING="J$@"

echo "Current Java:"
java -version
}

function _unsetJava()
{
if [ "$CURRENT_MODE_STRING" != "" ]; then
PATH="$ORIGINAL_PATH"
JAVA_HOME="$ORIGINAL_JAVA_HOME"
CURRENT_MODE_STRING=""
fi
}

function unsetJava()
{
echo "Configuring Shell Environment for default Java"
_unsetJava

echo "Current Java:"
java -version
}