Ben Summers’ blog

JRuby, Java and Ruby

JRuby is an alternative implementation of the Ruby programming language, implemented in Java and running on the JVM. It offers some useful advantages over the standard C interpreter:

  • it’s often faster
  • it offers easy integration with Java for access to vast amounts of useful Java libraries
  • you get real threads, instead of C ruby’s green threads

It’s also a benefit to the Ruby community at large, even if you don’t run it. The various Ruby implementations are all working together on RubySpec, a large test suite which is effectively defining the Ruby language and verifying that all the implementations are compatible.

Installation and running code

Installation really is as simple as promised by the web site. You download the archive, unzip it and add the bin directory to your PATH. Then you have a ruby compatible interpreter, jruby.

And after that, it “just works”. It behaves the same way as the standard ruby interpreter and, apart from a slightly longer startup time, performance is great.

Running jgem allows you to install gems, including Ruby on Rails. There is a slight limitation in that Ruby libraries with components written in C won’t work because they rely on the internals of the C ruby interpreter, but any pure-Ruby library will be fine. (note that you do need the bin directory in your PATH for jgem and the like to run, but jruby can be run using a full path to the interpreter)

JRuby isn’t quite pure Java itself. The distribution archive includes JNA to allow it access to the POSIX API for full compatibility with the C interpreter. This uses native code, and pre-compiled shared libraries are included in the distribution for various common OSes and architectures.

Ruby to Java integration

It’s incredibly easy to call Java code from Ruby code with JRuby. There’s plenty of examples on the Java integration wiki page.

This is wonderful, because there are lots of useful Java libraries. If you want to do something, there’s probably a tried and tested solution available. Even better, Java libraries are very easy to install — just download the jar file and put it in the CLASS_PATH. If you’ve ever had fun installing third party libraries (perhaps you don’t run Linux?) then this is a breath of fresh air!

JRuby goes out of its way to allow the code to be written in a Ruby style or in Java style. JRuby uses Java reflection to work out the Java types then converts the Ruby objects accordingly. It sounds like a lot of magic, but it generally works very well.

The only case where it breaks down is when Java generics are involved. For example, if you have a Map<String,String>, you will get some very unexpected types being inserted into the map if you call put(). Because of the backwards-compatible way Java generics work, during compilation the types are erased and the JVM sees the Map<String,String> object as a plain Map. When JRuby uses reflection to determine the signature of the put method, it has no idea that the object is expecting a String and doesn’t convert the Ruby object appropriately. This can result in some entertaining debugging sessions.

Java integration means you can write code which uses all the Java libraries with a much nicer syntax than Java itself, but there is going to be a performance hit with all the type conversion. In this case, it’s probably worth writing a bit of Java code and call into that with a single method call from Ruby.

For a “better Java”, something like Scala, a statically typed language which uses the underlying JVM types, may be a better choice. See James Strachan’s notes on Scala.

Java to Ruby integration

(This section applies to JRuby 1.3.1. As Charles Nutter (one of the JRuby developers) points out in the comments, there’s lots of work going on to make this easier in the next version.)

It’s obvious that the JRuby team expect you to start your programs using the jruby command, as calling Ruby from Java isn’t as pleasantly straightforward as the other way round. The wiki page on the subject isn’t as helpful as the equivalent for Ruby to Java, and most of the bits you need aren’t included in the standard JRuby distribution and have to be downloaded separately.

Two recommended methods are offered. The Java 6 scripting API looks the most promising, but I simply couldn’t get this to work. Investigations suggest that there aren’t the necessary entries in the manifest inside the relevant jar files to register the Ruby interpreter with the scripting API. I gave up on this one.

The other one is the Bean Scripting Framework, but it just seemed like yet more Java infrastructure to contend with. I have to confess I didn’t even try this one.

Finally, an alternative to the recommended methods is to call the JRuby APIs directly, which is discouraged because they may change over time. I doubt this is a huge problem since any new version of JRuby is going to require a good amount of testing and validation, and the interface between the two languages is quite small. And besides, there’s some useful methods defined in JavaEmbedUtils (found in a jar file which needs to be downloaded separately) which should be stable over time.

The wiki pages give a few pointers, but it took some experimentation to find a good way of integration. My eventual solution was a ‘boot’ script which required all the Ruby code and returned a Ruby object on which methods could be called by the Java. This avoids running eval all the time, which seems a bit of an inefficient way of calling Ruby methods.

  # Magic Ruby to Java integration require
  require ‘java’
  # Adjust the search path
  $: << CODE_ROOT
  # Require code as necessary
  require ‘some/ruby/code’
  # An interface class, to be called by the Java code
  class InterfaceObject
    def do_something(arg1, arg2)
      # ...
      some_string = “Results from Ruby code”
      Java::ComExamplePackage::Response.new(some_string)
    end
  end
  # Create a new interface object and store it in a constant
  MAIN_INTERFACE_OBJECT = InterfaceObject.new

This boot script is run on the Ruby side with:

  import org.jruby.Ruby;
  import org.jruby.RubyRuntimeAdapter;
  import org.jruby.javasupport.JavaEmbedUtils;  
  import org.jruby.runtime.builtin.IRubyObject;

  // ...

    // Get the root directory of the Ruby code
    String rootDir = ...;

    // Create a ruby runtime
    Ruby runtime = JavaEmbedUtils.initialize(new ArrayList());
    RubyRuntimeAdapter rubyEvaluater = JavaEmbedUtils.newRuntimeAdapter();
    
    // Set constants in the runtime, then run the boot.rb script
    IRubyObject interfaceObject = rubyEvaluater.eval(runtime,
        “CODE_ROOT = ‘"+rootDir+"'\n”+
        “require ‘"+rootDir+"/boot’\n”+
        “MAIN_INTERFACE_OBJECT”);
    
    // Check the result of the boot evaluation is the expected object
    if(interfaceObject == null || interfaceObject.isNil()
         || interfaceObject.isClass() || !interfaceObject.getMetaClass().getName().equals("InterfaceObject”))
    {
        throw new RuntimeException("Failed to obtain Ruby InterfaceObject object when booting”);
    }

After running this bootstrap code, interfaceObject is a Ruby object which can be called whenever required. To pass information to the boot script, constants are defined in the initial code passed to eval, in this example, CODE_ROOT is a directory name passed in by the Java code.

Once this object is set up, calling it is quite straightforward.

  import com.example.package.Response;

  // ...

    Response r = null;
    Object[] callargs = {arg1, arg2};
    Object r = JavaEmbedUtils.invokeMethod(runtime, interfaceObject, “do_something”, callargs, Response.class);
    response = (Response)r;

In this example, Response is a Java class I’ve defined to contain the result of the Ruby method. It’s created on the final line of the do_something method using the normal Ruby to Java integration.

To get this to run, you’ll need to work out the various options you need on the command line. The easiest way of working them out is to copy the jruby file, which is actually a long script, then replace the final exec which runs the java interpreter with an echo. Simply copy the arguments and adjust appropriately.

Here’s an example from my Solaris 10 system:

  java -d64 -server -Djruby.memory.max=500m -Djruby.stack.max=1024k -Xmx500m -Xss1024k \
    -Djna.boot.library.path=/opt/jruby/lib/native/sunos-x86:/opt/jruby/lib/native/sunos-amd64 \
    -Djffi.boot.library.path=/opt/jruby/lib/native/amd64-SunOS:/opt/jruby/lib/native/x86-SunOS \
    -Djruby.home=/opt/jruby -Djruby.lib=/opt/jruby/lib -Djruby.script=jruby -Djruby.shell=/bin/sh \
    -jar oneis.jar

This sets the environment expected by JRuby, then runs the program from the oneis.jar file.

Should you write Java which calls Ruby?

You have to decide what language starts your program. Using Ruby is easier — just run the jruby interpreter and it’s very simple. Using Java is more complex, but perfectly possible and gives more flexibility.

I choose to have Java in control of the process, so I could write an application server which met my exact requirements. But it’s not the easiest route.

Which Ruby implementation should you use?

At the moment, it seems to be a choice between the standard C Ruby interpreter and JRuby. Both are being used for deploying large applications. I suspect the thought process looks something like:

  • Are your Ruby scripts short and run often? Use C Ruby.
  • Would using Java libraries be useful? Use JRuby.
  • Would real threading help your application make efficient use of resources? Use JRuby.
  • Do you depend on a Ruby library with a C component? Use C Ruby.
  • If you benchmark your app on JRuby, is it significantly faster? Use JRuby.
  • Is C Ruby annoyingly packaged on your platform? Use JRuby.
  • Otherwise use C Ruby.

For my purposes, JRuby is fantastic. Instead of having Java code running in a separate process and communicating with multiple Ruby application servers over sockets, I can move everything into a single, properly multi-threaded process with seamless Java and Ruby integration.

 

COMMENTS

blog comments powered by Disqus

 

Hello, I’m Ben.

I’m the Technical Director of ONEIS, a platform for information management.

 

About this blog

 

Twitter: @bensummers

 

Subscribe