In December, JRuby a language that brings Ruby's easy syntax to the JVM turned ten years old. Over the course of those years, numerous companies have embraced it as their primary language implementation of choice. From the refueling systems at the Oslo airport in Norway, to the industrial laundries at some of the biggest hospitals in Minnesota, to software consultancies and Internet companies such as LinkedIn, JRuby delivers products for regular use.
This article doesn't aim to be an introduction to the Ruby world. Instead, I want to explain why the marriage of an expressive language like Ruby and the JVM is so convenient, and how developers can take advantage of it.
Where JRuby Improves on Ruby
When Ruby was ported to the JVM, its developers needed to make a controversial decision: whether to mimic the primary Ruby implementation piece by piece, or adapt the language to the platform, and thus make the language a "better" Ruby. They chose the latter.
The most notable adaptation was the introduction of a compiler. Other Ruby implementations run the code directly, executing the generated abstract syntax trees (AST) as the program is running. Although this approach works fine most of the time, it's not the most optimized way to run an application. Every time the program steps through the AST, it has to make decisions and perform the same actions repeatedly.
JRuby introduced a compiler with two strategies: ahead-of-time compilation (AOT) and just-in-time compilation (JIT). The first strategy compiles the Ruby code to Java bytecode as would happen with any a program written in a compiled language like Java or C. The second strategy compiles the code while the program is running. The JIT optimizes performance over time, especially when the same code is called frequently.
Another adaptation is the approach to threading. The primary Ruby implementation uses what are called "green threads" in its 1.8 branch; they are just swapped around every 10ms or so. The 1.9 branch uses real native threads, but the interpreter uses a Global Interpreter Lock (GIL) to keep them from running concurrently. In its early days, JRuby removed those limitations. Every thread is treated as an independent runner by the JVM without any lock between the interpreter and the operative system. This adaptation allows JRuby to take full advantage of multicore systems.
Consistent with these design choices, JRuby has been carefully adapted to run on the JVM. It provides several options to tune its performance from enabling experimental features, such as Java 7's invokedynamic support, to modifying the behavior of the JVM internals. The same flags that we can use to modify the memory allocation or the behavior of the garbage collector for Java can be used to modify the same parameters for JRuby.
Deep Integrating Between Java and Ruby
JRuby integrates both Java and Ruby, allowing them to interact with their original platforms. It takes the Java language and adapts it with Ruby idioms, but also allows it to run Ruby programs inside a scriptlet container from Java code. In this section though, I examine on how Java can be integrated with existing Ruby code.
ruby require 'java'
Everything starts with the simple command above. Before calling that instruction, JRuby communicates only with the Ruby world. The line of code loads the Java interoperability classes required to integrate both languages. The Ruby Kernel module is overloaded with new methods that will help us to work with Java classes.
With this code, we can instantiate any Java class using its fully qualified name (package and class name) and its constructor. As the example below shows, we use the same idiom that we already used to create an instance of a Ruby class.
ruby list = java.util.ArrayList.new
If we want to create several instances of the same class, we don't always need to use the full name of the class. JRuby adds two convenience methods to the Kernel to simplify this task and allow us to use just the class name. We can use java_import to include a Java class; after that, we no longer need to use the full name:
ruby java_import 'java.util.ArrayList' list = ArrayList.new
JRuby also provides a method called include_package, which we can use when we want to load any class that belongs to a Java package. It works only in Ruby modules, but it will help us to define common and less complex namespaces:
ruby module Util include_package 'java.util' end list = Util::ArrayList.new
There are other convenience additions beyond constructors and loading methods. For instance, the getters and setters into our Java classes are also aliased as Ruby attributes:
java
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ruby
person = Java::Person.new
person.name = 'John'
puts person.name
The integration is so deep that we can use Ruby metaprogramming techniques with any Java object. One fundamental aspect of Ruby is the concept of "open classes." This means that we can redefine any class at any moment in our code and add and remove behaviors. Java classes also allow this when run from JRuby. In this example, I show how to add the method map to a Java ArrayList:
ruby java_import 'java.util.ArrayList' class ArrayList def map new_array = [] it = iterator while it.has_next? new_array << yield(it.next) end new_array end end al = ArrayList.new([1, 2, 3]) new_al = al.map do |el| el + 1 end
Fortunately, we don't need to add Map to ArrayList because JRuby already includes the module Enumerable between the ancestors of the ArrayList, and it brings the method map along others to this class, as show here:
jruby-1.6.5 :001 > java.util.ArrayList.ancestors => [Java::JavaUtil::ArrayList, Java::JavaUtil::RandomAccess, Java::JavaLang::Cloneable, Java::JavaIo::Serializable, Java::JavaUtil::AbstractList, Java::JavaUtil::List, Java::JavaUtil::AbstractCollection, Java::JavaUtil::Collection, Java::JavaLang::Iterable, Enumerable, Java::JavaLang::Object, ConcreteJavaProxy, JavaProxy, JavaProxyMethods, Object, Kernel]
As you can see with these examples, even when we're working with Java classes, the code is fairly similar to Ruby code. I've only just scratched the surface of the Java interoperability. With this capability, JRuby opens the door to integration with other JVM languages. Scala, Clojure and any other language that compile to Java bytecode can be used with JRuby.


