Introduction to jRuby
by Kangqing Yu (Simon)
School of Computer Science
Carleton University
16 May 2018
About Me
-
BSc degree in Computer Science from University of Surrey, UK (2014)
-
BSc degree in Information Management and System from DUFE, China (Same year)
-
Software Developer at The ai Corporation, UK (2014-2015)
-
Software Developer at Lifecycle Software, UK (2015-2017)
-
Full-stack Rails Developer at MicroMetrics, Ottawa, Canada (2017 Summer breaks)
-
Currently a MSc student at School of Computer Science, Carleton University
What is jRuby
-
Still Ruby, but run on JVM
-
Interpreter implemented in Java
-
Tightly Integrated with Java with two-way access
-
Open sourced project, started in 2002 by Charles Oliver Nutter (Headius)
-
Aim to be compatible with MRI Ruby (Currently support 2.3)
How I met jRuby
-
Corporates don't prefer Rails
-
Rails has no explicit build step (code not compiled)
-
Great for managed service and multi-tenancy
-
But not ideal for enterprise and single-tenancy
Today's Agenda
-
jRuby programming
-
Install and compiler options
-
Java integration
-
Multi-threading
-
Memory management
-
jRuby Applications
-
jRuby on Rails
-
Further applications
Install jRuby
rvm install jruby #latest version 9.1.13.0
rvm use jruby
jruby version | MRI ruby version | Latest Java version supported |
---|---|---|
9.1.X | 2.3 | 9.0 |
9.0.X | 2.2 | 8.0 |
1.7.X | 1.9.3 | 8.0 |
-
Download at official site: http://jruby.org/download
-
Use RVM
jRuby Compiler
-
A Head Of Time Compilation (AOT)
#will output bytecode foo/bar/test.class
jrubyc foo/bar/test.rb
java -cp .:/path/to/jruby.jar foo.bar.test
-
Just-In-Time Compilation (JIT)
#JIT compile source code
jruby -S foo/bar/test.rb
- Ruby -> bytecode -> JVM
Some runtime options
-
jRuby runtime options:
-
invokedynamics
-
compile
-
jit
-
thread.pool
-
jruby --properties
-
JVM runtime options:
-
Heap space
-
Profiling
Configuring jRuby
-
Set JRUBY_OPTS environment variable
export JRUBY_OPTS="-X<property>=<value>"
-
Command Line Argument
jruby -J-Djruby.<property>=<value> -S foo.rb
-
In .jrubyrc file (after version 1.6.5)
<property>=<value>
Java Integration
-
Scripting Java from jRuby
-
Embedding Ruby in Java
-
Generating Java classes in jRuby
-
Java extension
Loading Java Classes
require 'java'
-
Load bootstrap classes:
-
Load additional classes:
require 'path/to/mycode.jar'
$CLASSPATH << "target/classes"
-
Use gems like jbundler, jar-dependencies to handle complex java dependencies
Referencing Java Classes
Java: org.foo.department.Widget
Ruby: Java::OrgFooDepartment::Widget
-
Full-qualified class name
-
Importing into a constant
java_import java.util.ArrayList
list = ArrayList.new
-
Importing into current namespace
module MyApp
include_package 'org.apache.logging.log4j'
Logger = LogManager.getLogger('MyApp')
end
Calling Java methods
-
Camel case <-> Snake case
java.lang.System.currentTimeMillis
java.lang.System.current_time_millis
-
Bean convensions
x.getSomething becomes x.something
x.setSomething(newValue) becomes x.something = new_value
x.isSomething becomes x.something?
-
Additional Java methods
java_class
java_kind_of? #the same as instanceof operator
java_object
java_send #based on reflection
java_method
java_alias
Inheriting (subclassing) Java classes
require 'java'
class HelloThread < java.lang.Thread
def run
puts 'hello world'
end
end
HelloThread.new.start
Note: use field_accessor to access protected fields
Implementing Java interfaces
require 'java'
class HelloThread
# This is optional as jruby supports duck-typing
include java.lang.Runnable
def run
puts "hello world"
end
end
java.lang.Thread.new(HelloThread.new).start
-
jRuby supports duck-typing
-
However, it is still necessary in some cases (see following)
Note:
public interface Balloon {
void pop();
}
public interface Bubble {
void pop();
}
public class Child{
public void give(Bubble bubble){
System.out.println("Thanks for the bubble.");
bubble.pop();
}
public void give(Balloon balloon){
System.out.println("Thanks for the balloon.");
balloon.pop();
}
}
require 'java'
class MyBalloon
#required as jruby runtime will
#use this to infer the type information
include Java::Ballon
def pop
puts 'Oh No!!!'
end
end
child = Java::Child.new
child.give(MylarBalloon.new)
Java:
jRuby:
Implementing Functional-style interfaces
button = new javax.swing.JButton("Press me!");
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
((javax.swing.JButton) e.getSource()).setText("You did it!");
}
});
button = javax.swing.JButton.new "Press me!"
button.add_action_listener do |event|
event.source.text = "You did it!"
end
- Ruby Proc style:
- Java anonymous class style:
Handling Exceptions from Java
begin
java.lang.Integer.parse_int("asdf")
rescue java.lang.NumberFormatException => e
puts "Failed to parse integer: #{e.message}"
puts e.print_stack_trace
end
-
Catch exceptions
-
Raise exceptions
raise java.lang.IllegalArgumentException.new("Bad param")
Java Enumerations
-
Include Enumerable module in Ruby by default
ni = java.net.NetworkInterface.networkInterfaces
puts ni.class.ancestors
#=> [#<Class:01x7e666f>, Java::JavaUtil::Enumeration, Enumerable,
# Java::JavaLang::Object, ConcreteJavaProxy, JavaProxy,
# JavaProxyMethods, Object, Java, Kernel]
java.net.NetworkInterface.networkInterfaces.each {|i| puts i }
-
Allocate new Java enumeration class
# Equivalent to Java's bytes = new byte[1024];
bytes = Java::byte[1024].new
list = java.util.ArrayList[1024].new
map = java.util.HashMap[1024].new
Type conversions
- Manual
[1,2,3].to_java
["a","b","c"].to_java(:string)
[1, 2, 3.5].to_java Java::double
java_arry.to_a
java_map.to_hash
java_set.to_set
bytes = 'a string'.to_java_bytes
string = String.from_java_bytes bytes
# works for InputStreams, OutputStreams, and NIO Channels
io = java_input_stream.to_io
# also to_outputstream and to_channel
stream = io.to_inputstream
Type conversions
- Automatic
Ruby Type | Java Type | Literal |
---|---|---|
String | java.lang.String | "foo" |
Fixnum | java.lang.Long, short, long, int, byte, char | 1 |
Float | java.lang.Double, java.lang.Float, float, double | 1.0 |
TrueClass, FalseClass | java.lang.Boolean | true, false |
Bignum | java.math.BigInteger | 1 << 128 |
NilClass | null | nil |
Overloading
-
Constructor signature
# Get the the three-integer constructor for this class
construct = JavaClass.java_class.constructor(Java::int, Java::int, Java::int)
object = construct.new_instance(0xa4, 0x00, 0x00)
-
Method signature
java_import java.util.ArrayList
list = ArrayList.new
list.java_send :add, [Java::int, java.lang.Object], 0, 'foo'
add = list.java_method :add, [Java::int, java.lang.Object]
add.call(0, 'foo')
class ArrayList
java_alias :simple_add, :add, [Java::int, java.lang.Object]
end
Multi-threading in jRuby
-
In 1.8.7, all threads map to a single native thread
-
In 2.0.0, each thread maps to a native OS thread (with GIL)
-
In jRuby, Ruby thread map to java.lang.Thread in Java (real-threading, with NO GIL) 😃
Parallelism
def bench_mark(n)
start = Time.now
1.upto(n).map do |n|
Thread.new { sleep(0.5) }
end.each(&:join)
Time.now - start
end
-
IO operations
Parallelism
Number of threads
Running time
Parallelism
def bench_mark(n)
start = Time.now
1.upto(n).map do |n|
Thread.new { fib(32) }
end.each(&:join)
Time.now - start
end
def fib(n)
n < 2 ? n : fib(n-1) + fib(n-2)
end
-
Arithmetic operations
Parallelism
Number of threads
Running time
Concurrency
-
Don't do it, if possible
-
If do it, don's share data
-
If share data, don's share mutable data
-
If share mutable data, make sure it is thread-safty
Rule of thumb:
Thread-safty
-
Synchronized keyword
obj.synchronized do
obj.wait 1000
end
-
Most operations in jruby are implemented thread-safely
-
Some gems might not be thread-safe e.g. dragonfly, json-schema
-
Checkout java.util.concurrent package and concurrent-ruby gem
Memory management
-
Mark and Sweep GC
-
Generational GC in JVM
-
Young generation && old generation
-
Major GC run && minor GC run
-
GCs are triggered lazily 😥
Memory management
Some tips:
-
Get rid of unused objects early
-
Set heap space manually by setting:
jruby -J-Xmn512m -J-Xms2048m -J-Xmx2048m -J-server
-
Do not enable ObjectSpace
-
Running Rails app in thread-safe mode
jRuby on Rails
-
C extension alternatives
-
Debugging
-
Dependency management
-
Deployment options
-
Background jobs
C extension alternatives for DB Driver
- Change DB driver adaptor
#Gemfile
gem 'activerecord-jdbcmysql-adapter'# for mysql
gem 'activerecord-jdbcsqlite3-adapter'# for sqlite
gem 'activerecord-jdbcpostgresql-adapter'# for postgres
-
Currently support Rails 5.1 (except for postgres)
- activerecord-jdbc-adaptor only support ActiveRecord 5.1 at the moment
- activerecord-jdbcpostgres-adaptor gem doesn't work with ActiveRecord 5.1
Check out https://github.com/jruby/activerecord-jdbc-adapter/issues/780
- Rails 4.2 is fully compatible
C Extension Alternatives for other gems
- Find an alternative gems that is written based on Java extension
-
pg -> activerecord-jdbcpostgresql-adaptor
-
memcached -> jruby-memcached
-
RMagick -> RMagick4J
-
therubyracer -> therubyrhino
-
...etc.
- If nothing is found, just use the pure Java versions! e.g. Nokogiri
Dependency Management
-
Use Bundler gem for managing gem dependencies
#Jarfile
# Custom repos which are not Maven Central
repository :aicorporation, "LOCAL_REPO_URL"
jar 'com.fasterxml.jackson.module:jackson-module-mrbean', '2.4.3'
jar 'com.fasterxml.jackson.core:jackson-core', '2.4.3'
jar 'com.fasterxml.jackson.core:jackson-databind', '2.4.3'
#in Gemfile
platform :jruby do
gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3.7'
end
platform :ruby do
gem 'pg', '~> 0.17.1'
end
-
Use jbundler gem for managing jar depedencies
Debugging
-
jruby-debug (not jRuby 9k compatible)
-
Gems byebug and pry-byebug are MRI only
-
These gems can be used for jRuby
# add the --debug flag to your ruby/jruby command
def index
binding.pry
@posts = Post.all
end
Deployment options
-
Traditional Rails Servers (Rack-based)
-
Puma
-
Trinidad (Tomcat-based)
-
TorqueBox (JBoss AS-based)
-
Mizuno, Jetty-Rails (Jetty-based)
-
War file deployment (J2EE-based)
-
Tomcat
-
Jetty
-
JBoss
-
GlassFish
-
WebLogic
-
WebSphere
Further Deployment options
-
Asynchronous web servers (non-blocking)
-
Thick & WildWeb (based on Netty NIO)
-
Jubilee (based on Vert.x)
package cz.wildweb.example;
import static cz.wildweb.api.Wildweb.*;
public class Main {
public static void main(String[] args) {
get("/", (req, res) -> { res.close("Welcome"); });
startServer(8080);
}
}
War file deployment
-
Add warbler gem
jruby -S gem install warbler
-
Generate config file and configure warble (optional)
# generate config file in config/warble.rb
warble config
-
Compile assets input public folder
jruby -S rake assets:precompile
-
Generate war file
# at root of rails project
warble
How it works
-
Rails is based on Rack interface (as middleware)
-
J2EE app servers are compliant with Java Servlet API
-
Warbler depends on jruby-rack gem that 'translate' the Rack-interface used in Rails into Java Servlet API
Rails Rack jruby-rack Java Servlet J2EE App Server
J2EE App
Rack App
// in web.xml file
<listener>
<listener-class>org.jruby.rack.rails.RailsServletContextListener</listener-class>
</listener>
Configuring warbler
-
config/warble.rb
-
config/web.xml.erb
# only these four features supported atm
config.features = %w(gemjar executable runnable compiled)
# Application directories to be included in the webapp.
config.dirs = %w(app config db lib log script vendor tmp)
# Public ROOT mapping
config.public.root = ''
# Value of RAILS_ENV for the webapp -- default as shown below
config.webxml.rails.env = ENV['RAILS_ENV'] || 'production'
# Control the pool of Rails runtimes.
config.webxml.jruby.min.runtimes = 2
config.webxml.jruby.max.runtimes = 4
- Two places to configure:
- Some commonly used options (config/warble.rb):
Background Jobs
-
Thread-based workers
-
On top of jruby-rack (runs in J2EE container)
-
Support Resque, Deplayed::Job,Navvy natively
-
Custom worker can be provided
-
mach_hare gem
-
jruby client for RabbitMQ
-
Similar to the bunny gem in MRI
Example from jruby-rack-worker
//in web.xml file
<context-param>
<param-name>jruby.worker</param-name>
<param-value>resque</param-value>
</context-param>
<context-param>
<param-name>QUEUES</param-name>
<param-value>mails,posts</param-value>
</context-param>
<context-param>
<param-name>INTERVAL</param-name>
<param-value>2.5</param-value>
</context-param>
<listener>
<listener-class>org.kares.jruby.rack.WorkerContextListener</listener-class>
</listener>
Things to take note
-
For Rails 2.x, you need to install jruby-openssl gem
jruby -S gem install jruby-openssl
-
Don't forget JCE Policy Files
-
Some jars in jBundler may not be packaged correctly in war file
#in config/warble.rb
config.java_libs += FileList["lib/java/*.jar"]
-
You might need this jar manny in some cases
#in Jarfile
jar 'org.bouncycastle:bcprov-jdk15on', '1.47'
Further applications in jRuby
-
Java Swing GUI
-
Spring MVC
-
JNDI
-
JMS
-
LDAP
-
.etc
Where to find more information
-
jRuby Wiki page
-
jRuby Cookbook by Justin Edelson
-
Deploying with jRuby by Joe Kutner
Thank you
My contact details:
-
simonykq@gmail.com
-
kangqingyu@cmail.carleton.ca
Questions?
Introduction to jRuby
By Simon Yu
Introduction to jRuby
Presentation slides for Ruby Meetup in Ottawa, 16 May 2018
- 373