Back in Chapter 2 I showed how JRuby executes a simple “puts 2+2” script. Now let’s take a look at how JRuby executes some Ruby code that is just a bit more complex, for example:
10.times do str = "The quick brown fox jumps over the lazy dog." puts str end
Here’s a conceptual diagram showing how JRuby will execute this script:
Let’s walk through what’s going on in this diagram:
On the top left is my Ruby script, this time called block.rb. JRuby will compile this into a Java class called block, named after the Ruby source file block.rb.
When it’s time to start executing my script the JVM will call the file method, which corresponds to the top level code in my script. This works the same way my “puts 2+2” JRuby example did back in Chapter 2. However, notice now there is another Java method in the generated block class called block_0$RUBY$__file__”. This oddly named method contains the compiled JVM byte code for the contents of my Ruby block.
Next while executing the call to 10.times, JRuby will call the RubyFixnum.times method, passing in the block as a parameter.
Now RubyFixnum.times will iterate 10 times, calling the block_0$RUBY$__file__ method each time through the loop.
Finally, the block’s code will in turn call the JRuby RubyIO Java class to print out the string.
The important detail to learn here is that JRuby's byte code compiler generates a separate Java method for each block or other scope in my Ruby script. Also note how JRuby passes control back and forth between the compiled version of my Ruby script and the Java classes that implement the Ruby’s built in classes such as Fixnum.
Now let’s take a second example that uses a closure:
str = "The quick brown fox" 10.times do str2 = "jumps over the lazy dog." puts "#{str} #{str2}" end
Here the block’s code refers to the str variable in the parent scope. JRuby compiles this script in same way as the previous example, generating a Java class that contains three methods. But how does JRuby allow the block to access the str from the parent scope? Does it use a DFP pointer and dynamic variable access like MRI does?
The answer is no. One of the important differences between JRuby and MRI is that JRuby does not use the JVM stack to keep track of different scopes or closure environments in your Ruby program, in the same way that MRI Ruby uses the YARV stack. There is no equivalent to the Dynamic Frame Pointer in JRuby. Instead, JRuby implements the same behavior using a series of Java classes, most importantly one called DynamicScope. When you reference variables from a different scope, i.e. when you use closures, JRuby saves the referenced variables inside a DynamicScope object. (Actually, there are a series of different Java classes that share DynamicScope as a common superclass that hold your closure variables.) In this example, JRuby saves my str variable inside a DynamicScope like this:
Later when JRuby executes the block, it creates a second DynamicScope object that refers to the parent scope like this:
Each DynamicScope object contains a parent pointer that indicates which other dynamic scope is the enclosing or parent scope for this closure. This is JRuby’s implementation of dynamic variable access. By iterating over these parent pointers, the JRuby Java code can get or set a variable that is present in a parent scope, or in the referencing environment of the closure.